dsh-plugin-subagent-director 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/lib/bridge-contract.d.ts +41 -0
  5. package/lib/bridge-contract.d.ts.map +1 -0
  6. package/lib/bridge-contract.js +7 -0
  7. package/lib/bridge-contract.js.map +1 -0
  8. package/lib/bridge-entry.d.ts +29 -0
  9. package/lib/bridge-entry.d.ts.map +1 -0
  10. package/lib/bridge-entry.js +10 -0
  11. package/lib/bridge-entry.js.map +1 -0
  12. package/lib/client/RoleCard.d.ts +23 -0
  13. package/lib/client/RoleCard.d.ts.map +1 -0
  14. package/lib/client/RoleCard.js +72 -0
  15. package/lib/client/RoleCard.js.map +1 -0
  16. package/lib/client/SubagentModelDock.d.ts +29 -0
  17. package/lib/client/SubagentModelDock.d.ts.map +1 -0
  18. package/lib/client/SubagentModelDock.js +32 -0
  19. package/lib/client/SubagentModelDock.js.map +1 -0
  20. package/lib/client/SubagentOptionsSection.d.ts +23 -0
  21. package/lib/client/SubagentOptionsSection.d.ts.map +1 -0
  22. package/lib/client/SubagentOptionsSection.js +164 -0
  23. package/lib/client/SubagentOptionsSection.js.map +1 -0
  24. package/lib/client/index.d.ts +44 -0
  25. package/lib/client/index.d.ts.map +1 -0
  26. package/lib/client/index.js +1541 -0
  27. package/lib/client/index.js.map +1 -0
  28. package/lib/client/locales.d.ts +13 -0
  29. package/lib/client/locales.d.ts.map +1 -0
  30. package/lib/client/locales.js +107 -0
  31. package/lib/client/locales.js.map +1 -0
  32. package/lib/client/store-logic.d.ts +105 -0
  33. package/lib/client/store-logic.d.ts.map +1 -0
  34. package/lib/client/store-logic.js +143 -0
  35. package/lib/client/store-logic.js.map +1 -0
  36. package/lib/client/store.d.ts +104 -0
  37. package/lib/client/store.d.ts.map +1 -0
  38. package/lib/client/store.js +223 -0
  39. package/lib/client/store.js.map +1 -0
  40. package/lib/client/subagent-model.d.ts +47 -0
  41. package/lib/client/subagent-model.d.ts.map +1 -0
  42. package/lib/client/subagent-model.js +81 -0
  43. package/lib/client/subagent-model.js.map +1 -0
  44. package/lib/client/ui.d.ts +26 -0
  45. package/lib/client/ui.d.ts.map +1 -0
  46. package/lib/client/ui.js +102 -0
  47. package/lib/client/ui.js.map +1 -0
  48. package/lib/config.d.ts +63 -0
  49. package/lib/config.d.ts.map +1 -0
  50. package/lib/config.js +26 -0
  51. package/lib/config.js.map +1 -0
  52. package/lib/delegation-tool.d.ts +169 -0
  53. package/lib/delegation-tool.d.ts.map +1 -0
  54. package/lib/delegation-tool.js +404 -0
  55. package/lib/delegation-tool.js.map +1 -0
  56. package/lib/envelope.d.ts +97 -0
  57. package/lib/envelope.d.ts.map +1 -0
  58. package/lib/envelope.js +136 -0
  59. package/lib/envelope.js.map +1 -0
  60. package/lib/guidance.d.ts +37 -0
  61. package/lib/guidance.d.ts.map +1 -0
  62. package/lib/guidance.js +46 -0
  63. package/lib/guidance.js.map +1 -0
  64. package/lib/index.d.ts +29 -0
  65. package/lib/index.d.ts.map +1 -0
  66. package/lib/index.js +63 -0
  67. package/lib/index.js.map +1 -0
  68. package/lib/remote.d.ts +102 -0
  69. package/lib/remote.d.ts.map +1 -0
  70. package/lib/remote.js +254 -0
  71. package/lib/remote.js.map +1 -0
  72. package/lib/route-resolver.d.ts +118 -0
  73. package/lib/route-resolver.d.ts.map +1 -0
  74. package/lib/route-resolver.js +71 -0
  75. package/lib/route-resolver.js.map +1 -0
  76. package/lib/settings.d.ts +157 -0
  77. package/lib/settings.d.ts.map +1 -0
  78. package/lib/settings.js +118 -0
  79. package/lib/settings.js.map +1 -0
  80. package/package.json +110 -0
@@ -0,0 +1,1541 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-plugin-subagent-director",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_web_react = require("@deepseek-ai/dsh-client-web-react");
8
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
9
+ let react = require("react");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ //#region src/client/locales.ts
12
+ /** English dictionary ($subagentDirector). */
13
+ const en = {
14
+ "nav": "Subagent Director",
15
+ "sectionIntro": "Choose the LLM provider and model each subagent uses, and define role templates that bind a model and persona to a delegation.",
16
+ "defaultsHeading": "Default model",
17
+ "defaultsHint": "Used when a call or role does not name a provider/model.",
18
+ "defaultProvider": "Provider",
19
+ "defaultModel": "Model",
20
+ "defaultReasoningEffort": "Reasoning effort",
21
+ "restoreDefaults": "Restore defaults",
22
+ "provider": "Provider",
23
+ "model": "Model",
24
+ "reasoningEffort": "Reasoning effort",
25
+ "persona": "Persona",
26
+ "rolesHeading": "Role templates",
27
+ "rolesHint": "A role binds delegation guidance, a persona, and optionally a provider/model to a subagent.",
28
+ "addRole": "Add role",
29
+ "emptyRoles": "No role templates yet. Add one to start planning subagent responsibilities.",
30
+ "roleId": "Role id",
31
+ "roleDisplayName": "Display name",
32
+ "roleDescription": "Delegation guidance",
33
+ "rolePersona": "Persona",
34
+ "setDefaultRole": "Set as default",
35
+ "defaultRoleBadge": "Default",
36
+ "deleteRole": "Delete",
37
+ "confirmDeleteRole": "Delete role “{id}”? Roles referenced by defaultRole will fall back to the plugin default.",
38
+ "removeRoleDone": "Role removed",
39
+ "addedRole": "Role added",
40
+ "roleUpdated": "Role updated",
41
+ "conflict": "The settings changed on the server. Reloading your edits — please review and retry.",
42
+ "rejected": "The change was rejected by the settings provider.",
43
+ "fatal": "Could not save: {message}",
44
+ "loadError": "Could not load preference settings.",
45
+ "readonly": "Settings are read-only in this deployment.",
46
+ "save": "Save",
47
+ "cancel": "Cancel",
48
+ "fallbackOnInvalid": "Fall back to the parent agent when a role-bound model is invalid",
49
+ "roleIdPlaceholder": "e.g. code-reviewer",
50
+ "displayNamePlaceholder": "e.g. Code Reviewer",
51
+ "descriptionPlaceholder": "When to delegate to this role and what it should do…",
52
+ "personaPlaceholder": "Optional behavior/identity text injected into the subagent…",
53
+ "modelOptional": "Save the card to apply changes",
54
+ "providerMissing": "No provider selected",
55
+ "restoreDone": "Defaults restored",
56
+ "invalidRoleId": "Role id must be kebab-case (lowercase letters, digits, single hyphens).",
57
+ "bridgeUnavailable": "The Subagent Director settings bridge is not available on this server. Please update/restart the web application so it installs the /subagent-director channel.",
58
+ "modelRanOn": "Subagent ran on",
59
+ "modelNotRecorded": "Subagent model not recorded yet",
60
+ "modelRanOnTitle": "Model route: {model}"
61
+ };
62
+ /** Chinese dictionary. */
63
+ const zh = {
64
+ "nav": "子代理导演",
65
+ "sectionIntro": "为每个子代理选择 LLM 供应商与模型,并以角色模板把模型与 persona 绑定到每次委派。",
66
+ "defaultsHeading": "默认模型",
67
+ "defaultsHint": "当某次调用或角色未指定供应商/模型时使用。",
68
+ "defaultProvider": "供应商",
69
+ "defaultModel": "模型",
70
+ "defaultReasoningEffort": "推理强度",
71
+ "restoreDefaults": "恢复默认",
72
+ "provider": "供应商",
73
+ "model": "模型",
74
+ "reasoningEffort": "推理强度",
75
+ "persona": "人设",
76
+ "rolesHeading": "角色模板",
77
+ "rolesHint": "角色把委派指引、persona 与可选的供应商/模型绑定到子代理。",
78
+ "addRole": "添加角色",
79
+ "emptyRoles": "还没有角色模板。添加一个以开始规划子代理职责。",
80
+ "roleId": "角色 id",
81
+ "roleDisplayName": "显示名称",
82
+ "roleDescription": "委派指引",
83
+ "rolePersona": "人设",
84
+ "setDefaultRole": "设为默认",
85
+ "defaultRoleBadge": "默认",
86
+ "deleteRole": "删除",
87
+ "confirmDeleteRole": "删除角色“{id}”?若 defaultRole 引用了它,将回退到插件默认值。",
88
+ "removeRoleDone": "角色已删除",
89
+ "addedRole": "角色已添加",
90
+ "roleUpdated": "角色已更新",
91
+ "conflict": "服务端设置已变更。已重新加载你的编辑——请复核并重试。",
92
+ "rejected": "更改被设置提供方拒绝。",
93
+ "fatal": "无法保存:{message}",
94
+ "loadError": "无法加载偏好设置。",
95
+ "readonly": "当前部署中设置为只读。",
96
+ "save": "保存",
97
+ "cancel": "取消",
98
+ "fallbackOnInvalid": "当角色绑定的模型无效时回退到父代理",
99
+ "roleIdPlaceholder": "例如 code-reviewer",
100
+ "displayNamePlaceholder": "例如 代码审查员",
101
+ "descriptionPlaceholder": "何时委派给此角色、让其做什么……",
102
+ "personaPlaceholder": "注入子代理的可选行为/身份文案……",
103
+ "modelOptional": "保存卡片以应用更改",
104
+ "providerMissing": "未选择供应商",
105
+ "restoreDone": "已恢复默认",
106
+ "invalidRoleId": "角色 id 必须为 kebab-case(小写字母、数字、单个连字符)。",
107
+ "bridgeUnavailable": "此服务器尚未提供 Subagent Director 设置桥接通道(/subagent-director)。请重启 Web 应用以安装该通道后再试。",
108
+ "modelRanOn": "子代理实际运行于",
109
+ "modelNotRecorded": "尚未记录到子代理模型",
110
+ "modelRanOnTitle": "模型路由:{model}"
111
+ };
112
+ //#endregion
113
+ //#region src/bridge-contract.ts
114
+ /** Absolute RPC channel the bridge owns on the Host web server. */
115
+ const SUBAGENT_DIRECTOR_RPC_CHANNEL = "/subagent-director";
116
+ /** Endpoint that returns the namespace's redacted wire view. */
117
+ const SUBAGENT_DIRECTOR_RPC_VIEW = "settingsView";
118
+ /** Endpoint that applies one path-op mutation. */
119
+ const SUBAGENT_DIRECTOR_RPC_MUTATE = "settingsMutate";
120
+ //#endregion
121
+ //#region src/client/store-logic.ts
122
+ /** Path of the roles map from the section root. */
123
+ const ROLES_PATH = ["roles"];
124
+ /** Normalize an optional string: blank/whitespace becomes undefined (removed). */
125
+ function optional(value) {
126
+ if (value === void 0) return void 0;
127
+ const trimmed = value.trim();
128
+ return trimmed.length === 0 ? void 0 : trimmed;
129
+ }
130
+ /** Generate a kebab-case id from a display name; falls back to a prefix + counter. */
131
+ function roleIdFromName(name, existing, prefix = "role") {
132
+ const base = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
133
+ const candidate = base.length > 0 ? base : prefix;
134
+ if (!existing.has(candidate)) return candidate;
135
+ for (let i = 2; i < 1e3; i++) if (!existing.has(candidate + "-" + i)) return candidate + "-" + i;
136
+ return candidate + "-" + Date.now().toString(36);
137
+ }
138
+ /**
139
+ * Build the path ops that create or fully replace one role. A single set at the
140
+ * role's root writes every field at once (mutate resolves against the stored
141
+ * section, so intermediate objects materialize).
142
+ */
143
+ function addRoleOps(id, role) {
144
+ return [{
145
+ op: "set",
146
+ path: [ROLES_PATH[0], id],
147
+ value: {
148
+ displayName: role.displayName,
149
+ description: role.description,
150
+ ...optional(role.persona) !== void 0 ? { persona: optional(role.persona) } : {},
151
+ ...optional(role.provider) !== void 0 ? { provider: optional(role.provider) } : {},
152
+ ...optional(role.model) !== void 0 ? { model: optional(role.model) } : {},
153
+ ...optional(role.reasoningEffort) !== void 0 ? { reasoningEffort: optional(role.reasoningEffort) } : {}
154
+ }
155
+ }];
156
+ }
157
+ /** Field-by-field set-edits when a value changed from the stored role; unset when cleared. */
158
+ function fieldEdit(path, stored, next) {
159
+ const store = typeof stored === "string" ? stored : void 0;
160
+ const normalized = optional(next);
161
+ if (normalized === store) return void 0;
162
+ if (normalized === void 0) return {
163
+ op: "unset",
164
+ path: [...path]
165
+ };
166
+ return {
167
+ op: "set",
168
+ path: [...path],
169
+ value: normalized
170
+ };
171
+ }
172
+ /**
173
+ * Diff one role between its stored value and the edited draft. Only changed
174
+ * fields become ops; clearing a field becomes an unset that restores the
175
+ * composition base / removes the user override.
176
+ */
177
+ function updateRoleOps(id, before, draft) {
178
+ const b = before ?? {};
179
+ const ops = [];
180
+ const push = (op) => {
181
+ if (op) ops.push(op);
182
+ };
183
+ const base = [ROLES_PATH[0], id];
184
+ push(fieldEdit([...base, "displayName"], b.displayName, draft.displayName));
185
+ push(fieldEdit([...base, "description"], b.description, draft.description));
186
+ push(fieldEdit([...base, "persona"], b.persona, draft.persona));
187
+ push(fieldEdit([...base, "provider"], b.provider, draft.provider));
188
+ push(fieldEdit([...base, "model"], b.model, draft.model));
189
+ push(fieldEdit([...base, "reasoningEffort"], b.reasoningEffort, draft.reasoningEffort));
190
+ return ops;
191
+ }
192
+ /**
193
+ * Ops to remove one role. When it was the defaultRole, the reference is cleared
194
+ * too so a stale default never points at a removed role.
195
+ */
196
+ function removeRoleOps(id, current) {
197
+ const ops = [{
198
+ op: "unset",
199
+ path: [ROLES_PATH[0], id]
200
+ }];
201
+ if (current.defaultRole === id) ops.push({
202
+ op: "unset",
203
+ path: ["defaultRole"]
204
+ });
205
+ return ops;
206
+ }
207
+ /** Ops to promote one role to the default. */
208
+ function setDefaultRoleOps(id) {
209
+ return [{
210
+ op: "set",
211
+ path: ["defaultRole"],
212
+ value: id
213
+ }];
214
+ }
215
+ function defaultModelOps(before, edits) {
216
+ const ops = [];
217
+ const push = (op) => {
218
+ if (op) ops.push(op);
219
+ };
220
+ push(fieldEdit(["defaultProvider"], before.defaultProvider, edits.provider));
221
+ push(fieldEdit(["defaultModel"], before.defaultModel, edits.model));
222
+ push(fieldEdit(["defaultReasoningEffort"], before.defaultReasoningEffort, edits.reasoningEffort));
223
+ return ops;
224
+ }
225
+ /** Ops to clear every default-model field and the defaultRole back to composition defaults. */
226
+ function restoreDefaultsOps(current) {
227
+ const ops = [];
228
+ if (current.defaultProvider !== void 0) ops.push({
229
+ op: "unset",
230
+ path: ["defaultProvider"]
231
+ });
232
+ if (current.defaultModel !== void 0) ops.push({
233
+ op: "unset",
234
+ path: ["defaultModel"]
235
+ });
236
+ if (current.defaultReasoningEffort !== void 0) ops.push({
237
+ op: "unset",
238
+ path: ["defaultReasoningEffort"]
239
+ });
240
+ if (current.defaultRole !== void 0) ops.push({
241
+ op: "unset",
242
+ path: ["defaultRole"]
243
+ });
244
+ return ops;
245
+ }
246
+ /** Map an RPC error code to a UI outcome; unknown/undefined errors are fatal. */
247
+ function classifyMutateError(code, _message) {
248
+ if (code === "settings-conflict") return "conflict";
249
+ if (code === "settings-rejected" || code === "schema-validation") return "rejected";
250
+ return "fatal";
251
+ }
252
+ //#endregion
253
+ //#region src/client/store.ts
254
+ /** The settings namespace this page reads and writes. */
255
+ const SUBAGENT_DIRECTOR_NS = "subagent-director";
256
+ /** Initial empty snapshot. */
257
+ function initialSubagentOptionsState() {
258
+ return {
259
+ status: "idle",
260
+ error: null,
261
+ writable: true,
262
+ namespace: void 0,
263
+ section: void 0,
264
+ revision: 0,
265
+ providers: [],
266
+ models: [],
267
+ loading: false
268
+ };
269
+ }
270
+ /** Human text for a rejected wire call. */
271
+ function messageOf(error) {
272
+ if (error instanceof Error) return error.message;
273
+ return typeof error === "string" ? error : String(error);
274
+ }
275
+ /**
276
+ * Raised when the /subagent-director bridge channel is not reachable on the
277
+ * current transport (a Host that predates the bridge answers nothing). The
278
+ * store surfaces this as the localized `bridgeUnavailable` copy instead of a
279
+ * raw transport string.
280
+ */
281
+ var BridgeUnavailableError = class extends Error {
282
+ constructor() {
283
+ super("Subagent Director settings bridge (/subagent-director) is not available on this server");
284
+ }
285
+ };
286
+ /** Whether a thrown value means the bridge channel could not be called at all. */
287
+ function isBridgeUnavailable(error) {
288
+ return error instanceof BridgeUnavailableError;
289
+ }
290
+ /** The settings page controller (one per settings surface). */
291
+ var SubagentOptionsStore = class {
292
+ store;
293
+ wire;
294
+ generation = 0;
295
+ constructor(wire) {
296
+ this.wire = wire;
297
+ this.store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(initialSubagentOptionsState());
298
+ }
299
+ /**
300
+ * Call one bridge endpoint over the generic RPC channel. Returns the RpcResult
301
+ * (ok or error branch). A transport-level rejection — the Host has no
302
+ * /subagent-director channel — is folded into BridgeUnavailableError so the
303
+ * caller can show the localized message.
304
+ */
305
+ async callBridge(endpoint, payload) {
306
+ let result;
307
+ try {
308
+ result = await this.wire.rpc.call(SUBAGENT_DIRECTOR_RPC_CHANNEL, endpoint, payload);
309
+ } catch (error) {
310
+ throw new BridgeUnavailableError();
311
+ }
312
+ if (!result.ok) return {
313
+ ok: false,
314
+ error: result.error
315
+ };
316
+ return {
317
+ ok: true,
318
+ value: result.value
319
+ };
320
+ }
321
+ /** Refresh the whole page snapshot: provider directory + model catalog + own namespace. */
322
+ async load() {
323
+ const generation = ++this.generation;
324
+ this.store.update((s) => {
325
+ s.status = "loading";
326
+ s.error = null;
327
+ s.loading = true;
328
+ });
329
+ try {
330
+ const [providersResponse, modelsResponse, viewResult] = await Promise.all([
331
+ this.wire.llm.providers({}),
332
+ this.wire.llm.models({}),
333
+ this.callBridge(SUBAGENT_DIRECTOR_RPC_VIEW)
334
+ ]);
335
+ if (!providersResponse.result.ok) throw new Error(providersResponse.result.error.message);
336
+ if (!modelsResponse.result.ok) throw new Error(modelsResponse.result.error.message);
337
+ if (!viewResult.ok) throw new Error(this.errorMessage(viewResult.error));
338
+ if (generation !== this.generation) return;
339
+ const view = viewResult.value.view;
340
+ const section = view?.value ?? {};
341
+ const writable = viewResult.value.writable;
342
+ const providers = providersResponse.result.value.providers;
343
+ const models = modelsResponse.result.value.groups;
344
+ this.store.update((s) => {
345
+ s.status = "ready";
346
+ s.error = null;
347
+ s.writable = writable;
348
+ s.providers = providers;
349
+ s.models = models;
350
+ s.namespace = view;
351
+ s.section = section;
352
+ s.revision = view?.revision ?? 0;
353
+ s.loading = false;
354
+ });
355
+ } catch (error) {
356
+ if (generation !== this.generation) return;
357
+ this.store.update((s) => {
358
+ s.status = "error";
359
+ s.error = isBridgeUnavailable(error) ? this.wire.t("bridgeUnavailable") : messageOf(error);
360
+ s.loading = false;
361
+ });
362
+ }
363
+ }
364
+ /** Human text for a bridge RPC error branch. */
365
+ errorMessage(error) {
366
+ if (error !== null && typeof error === "object" && "message" in error) {
367
+ const message = error.message;
368
+ if (typeof message === "string") return message;
369
+ }
370
+ return messageOf(error);
371
+ }
372
+ /**
373
+ * Run one mutate and update the snapshot's revision. Returns a failure
374
+ * message (localized by the caller) or undefined on success. A
375
+ * settings-conflict re-reads the namespace and returns the conflict kind so
376
+ * the UI can show the "please review and retry" message.
377
+ */
378
+ async mutate(ops) {
379
+ const state = this.store.getSnapshot();
380
+ const ns = SUBAGENT_DIRECTOR_NS;
381
+ const revision = state.revision;
382
+ let result;
383
+ try {
384
+ result = await this.callBridge(SUBAGENT_DIRECTOR_RPC_MUTATE, {
385
+ ns,
386
+ ops,
387
+ expectedRevision: revision
388
+ });
389
+ } catch (error) {
390
+ return {
391
+ ok: false,
392
+ kind: "fatal",
393
+ message: isBridgeUnavailable(error) ? this.wire.t("bridgeUnavailable") : messageOf(error)
394
+ };
395
+ }
396
+ if (result.ok) {
397
+ const admitted = result.value;
398
+ this.store.update((s) => {
399
+ s.revision = admitted.revision;
400
+ s.namespace = admitted;
401
+ s.section = admitted.value ?? {};
402
+ });
403
+ return {
404
+ ok: true,
405
+ kind: "fatal",
406
+ message: void 0
407
+ };
408
+ }
409
+ const code = this.errorCode(result.error);
410
+ const message = this.errorMessage(result.error);
411
+ const kind = classifyMutateError(code, message);
412
+ if (kind === "conflict") {
413
+ await this.reloadNamespace();
414
+ return {
415
+ ok: false,
416
+ kind,
417
+ message
418
+ };
419
+ }
420
+ return {
421
+ ok: false,
422
+ kind,
423
+ message
424
+ };
425
+ }
426
+ async reloadNamespace() {
427
+ try {
428
+ const result = await this.callBridge(SUBAGENT_DIRECTOR_RPC_VIEW);
429
+ if (!result.ok) return;
430
+ const view = result.value.view;
431
+ if (!view) return;
432
+ this.store.update((s) => {
433
+ s.namespace = view;
434
+ s.section = view.value ?? {};
435
+ s.revision = view.revision;
436
+ s.writable = result.value.writable;
437
+ });
438
+ } catch {}
439
+ }
440
+ /** Error code of a bridge RPC error branch, when present. */
441
+ errorCode(error) {
442
+ if (error !== null && typeof error === "object" && "code" in error) {
443
+ const code = error.code;
444
+ return typeof code === "string" ? code : void 0;
445
+ }
446
+ }
447
+ async addRole(id, draft) {
448
+ const result = await this.mutate(addRoleOps(id, draft));
449
+ return result.ok ? void 0 : result.message;
450
+ }
451
+ async updateRole(id, before, draft) {
452
+ const result = await this.mutate(updateRoleOps(id, before, draft));
453
+ return result.ok ? void 0 : result.message;
454
+ }
455
+ async removeRole(id) {
456
+ const state = this.store.getSnapshot();
457
+ const result = await this.mutate(removeRoleOps(id, { defaultRole: state.section?.defaultRole }));
458
+ return result.ok ? void 0 : result.message;
459
+ }
460
+ async setDefaultRole(id) {
461
+ const result = await this.mutate(setDefaultRoleOps(id));
462
+ return result.ok ? void 0 : result.message;
463
+ }
464
+ async setDefaultModel(edits) {
465
+ const state = this.store.getSnapshot();
466
+ const result = await this.mutate(defaultModelOps(state.section ?? {}, edits));
467
+ return result.ok ? void 0 : result.message;
468
+ }
469
+ async restoreDefaults() {
470
+ const state = this.store.getSnapshot();
471
+ const result = await this.mutate(restoreDefaultsOps(state.section ?? {}));
472
+ return result.ok ? void 0 : result.message;
473
+ }
474
+ };
475
+ //#endregion
476
+ //#region src/client/ui.ts
477
+ const token = {
478
+ labelPrimary: "var(--dsw-alias-label-primary)",
479
+ labelSecondary: "var(--dsw-alias-label-secondary)",
480
+ labelTertiary: "var(--dsw-alias-label-tertiary)",
481
+ border: "var(--dsw-alias-border-l2)",
482
+ bgLayer1: "var(--dsw-alias-bg-layer-1)",
483
+ bgLayer3: "var(--dsw-alias-bg-layer-3)",
484
+ accent: "var(--dsw-alias-state-business-primary)",
485
+ danger: "var(--dsw-alias-state-error-primary)",
486
+ shadow: "var(--dsw-shadow-lv1)"
487
+ };
488
+ const rowStyle = {
489
+ display: "flex",
490
+ flexDirection: "column",
491
+ gap: 6
492
+ };
493
+ const fieldLabelStyle = {
494
+ color: token.labelSecondary,
495
+ fontSize: 12,
496
+ lineHeight: "16px"
497
+ };
498
+ const selectStyle = {
499
+ height: 30,
500
+ borderRadius: 6,
501
+ border: "1px solid " + token.border,
502
+ background: token.bgLayer1,
503
+ color: token.labelPrimary,
504
+ font: "inherit",
505
+ fontSize: 13,
506
+ padding: "0 8px",
507
+ outline: "none"
508
+ };
509
+ const textInputStyle = {
510
+ height: 30,
511
+ borderRadius: 6,
512
+ border: "1px solid " + token.border,
513
+ background: token.bgLayer1,
514
+ color: token.labelPrimary,
515
+ font: "inherit",
516
+ fontSize: 13,
517
+ padding: "0 8px",
518
+ outline: "none"
519
+ };
520
+ const textAreaStyle = {
521
+ borderRadius: 6,
522
+ border: "1px solid " + token.border,
523
+ background: token.bgLayer1,
524
+ color: token.labelPrimary,
525
+ font: "inherit",
526
+ fontSize: 13,
527
+ lineHeight: "18px",
528
+ padding: "6px 8px",
529
+ resize: "vertical",
530
+ minHeight: 56,
531
+ outline: "none"
532
+ };
533
+ const primaryButtonStyle = {
534
+ height: 28,
535
+ borderRadius: 6,
536
+ border: "1px solid " + token.accent,
537
+ background: token.accent,
538
+ color: "#fff",
539
+ font: "inherit",
540
+ fontSize: 13,
541
+ cursor: "pointer",
542
+ padding: "0 12px"
543
+ };
544
+ const ghostButtonStyle = {
545
+ height: 28,
546
+ borderRadius: 6,
547
+ border: "1px solid " + token.border,
548
+ background: "transparent",
549
+ color: token.labelPrimary,
550
+ font: "inherit",
551
+ fontSize: 13,
552
+ cursor: "pointer",
553
+ padding: "0 12px"
554
+ };
555
+ const dangerButtonStyle = {
556
+ ...ghostButtonStyle,
557
+ color: token.danger,
558
+ borderColor: token.danger
559
+ };
560
+ const cardStyle = {
561
+ border: "1px solid " + token.border,
562
+ background: token.bgLayer3,
563
+ borderRadius: 10,
564
+ padding: 12,
565
+ display: "flex",
566
+ flexDirection: "column",
567
+ gap: 10,
568
+ minWidth: 0
569
+ };
570
+ const sectionWidth = {
571
+ width: "100%",
572
+ maxWidth: 760,
573
+ display: "flex",
574
+ flexDirection: "column",
575
+ gap: 16,
576
+ color: token.labelPrimary
577
+ };
578
+ //#endregion
579
+ //#region src/client/RoleCard.tsx
580
+ /** One role template card: read-only summary plus an inline editor.
581
+ * Provider/model/effort selects cascade from the model catalog; writes go
582
+ * through the controller as path ops, and failures return a localized message. */
583
+ function effortsFor$1(groups, provider, model) {
584
+ if (!provider || !model) return [];
585
+ return (groups.find((g) => g.id === provider)?.models.find((m) => m.id === model))?.reasoning?.efforts ?? [];
586
+ }
587
+ function RoleCard({ id, role, isDefault, groups, t, onSave, onDelete, onSetDefault }) {
588
+ const [editing, setEditing] = (0, react.useState)(false);
589
+ const [busy, setBusy] = (0, react.useState)(false);
590
+ const [failure, setFailure] = (0, react.useState)(void 0);
591
+ const [draft, setDraft] = (0, react.useState)({
592
+ displayName: role.displayName,
593
+ description: role.description,
594
+ persona: role.persona ?? "",
595
+ provider: role.provider ?? "",
596
+ model: role.model ?? "",
597
+ reasoningEffort: role.reasoningEffort ?? ""
598
+ });
599
+ const provider = draft.provider || role.provider;
600
+ const model = draft.model || role.model;
601
+ const modelOptions = provider ? groups.find((g) => g.id === provider)?.models ?? [] : [];
602
+ const effortOptions = effortsFor$1(groups, provider, model);
603
+ const save = async () => {
604
+ setBusy(true);
605
+ setFailure(void 0);
606
+ try {
607
+ const message = await onSave(draft);
608
+ if (message !== void 0) {
609
+ setFailure(message);
610
+ return;
611
+ }
612
+ setEditing(false);
613
+ } finally {
614
+ setBusy(false);
615
+ }
616
+ };
617
+ const remove = async () => {
618
+ if (!window.confirm(t("confirmDeleteRole").replace("{id}", id))) return;
619
+ setBusy(true);
620
+ setFailure(void 0);
621
+ try {
622
+ const message = await onDelete();
623
+ if (message !== void 0) setFailure(message);
624
+ } finally {
625
+ setBusy(false);
626
+ }
627
+ };
628
+ const setField = (field, value) => {
629
+ setDraft((d) => ({
630
+ ...d,
631
+ [field]: value
632
+ }));
633
+ };
634
+ if (editing) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
635
+ style: cardStyle,
636
+ children: [
637
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
638
+ style: {
639
+ display: "flex",
640
+ alignItems: "baseline",
641
+ gap: 8
642
+ },
643
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
644
+ style: {
645
+ color: token.labelPrimary,
646
+ fontSize: 14
647
+ },
648
+ children: t("roleDisplayName")
649
+ }), isDefault ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
650
+ style: {
651
+ color: token.accent,
652
+ fontSize: 12
653
+ },
654
+ children: t("defaultRoleBadge")
655
+ }) : null]
656
+ }),
657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
658
+ style: rowStyle,
659
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
660
+ style: fieldLabelStyle,
661
+ children: t("roleDisplayName")
662
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
663
+ style: textInputStyle,
664
+ value: draft.displayName,
665
+ placeholder: t("displayNamePlaceholder"),
666
+ onChange: (e) => setField("displayName", e.target.value)
667
+ })]
668
+ }),
669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
670
+ style: rowStyle,
671
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
672
+ style: fieldLabelStyle,
673
+ children: t("roleDescription")
674
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
675
+ style: textAreaStyle,
676
+ value: draft.description,
677
+ placeholder: t("descriptionPlaceholder"),
678
+ onChange: (e) => setField("description", e.target.value)
679
+ })]
680
+ }),
681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
682
+ style: rowStyle,
683
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
684
+ style: fieldLabelStyle,
685
+ children: t("rolePersona")
686
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
687
+ style: textAreaStyle,
688
+ value: draft.persona,
689
+ placeholder: t("personaPlaceholder"),
690
+ onChange: (e) => setField("persona", e.target.value)
691
+ })]
692
+ }),
693
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
694
+ style: {
695
+ display: "grid",
696
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
697
+ gap: 8
698
+ },
699
+ children: [
700
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
701
+ style: rowStyle,
702
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
703
+ style: fieldLabelStyle,
704
+ children: t("provider")
705
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
706
+ style: selectStyle,
707
+ value: draft.provider,
708
+ disabled: groups.length === 0,
709
+ onChange: (e) => setField("provider", e.target.value),
710
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
711
+ value: "",
712
+ children: "—"
713
+ }), groups.map((g) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
714
+ value: g.id,
715
+ children: g.name
716
+ }, g.id))]
717
+ })]
718
+ }),
719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
720
+ style: rowStyle,
721
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
722
+ style: fieldLabelStyle,
723
+ children: t("model")
724
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
725
+ style: selectStyle,
726
+ value: draft.model,
727
+ disabled: modelOptions.length === 0,
728
+ onChange: (e) => setField("model", e.target.value),
729
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
730
+ value: "",
731
+ children: "—"
732
+ }), modelOptions.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
733
+ value: m.id,
734
+ children: m.name
735
+ }, m.id))]
736
+ })]
737
+ }),
738
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
739
+ style: rowStyle,
740
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
741
+ style: fieldLabelStyle,
742
+ children: t("reasoningEffort")
743
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
744
+ style: selectStyle,
745
+ value: draft.reasoningEffort,
746
+ disabled: effortOptions.length === 0,
747
+ onChange: (e) => setField("reasoningEffort", e.target.value),
748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
749
+ value: "",
750
+ children: "—"
751
+ }), effortOptions.map((e) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
752
+ value: e.id,
753
+ children: e.name
754
+ }, e.id))]
755
+ })]
756
+ })
757
+ ]
758
+ }),
759
+ failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
760
+ style: {
761
+ color: token.danger,
762
+ fontSize: 12
763
+ },
764
+ children: failure
765
+ }) : null,
766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
767
+ style: {
768
+ display: "flex",
769
+ gap: 8
770
+ },
771
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
772
+ style: primaryButtonStyle,
773
+ disabled: busy,
774
+ onClick: save,
775
+ children: "Edit"
776
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
777
+ style: ghostButtonStyle,
778
+ disabled: busy,
779
+ onClick: () => setEditing(false),
780
+ children: t("cancel")
781
+ })]
782
+ })
783
+ ]
784
+ });
785
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
786
+ style: cardStyle,
787
+ children: [
788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
789
+ style: {
790
+ display: "flex",
791
+ alignItems: "baseline",
792
+ justifyContent: "space-between",
793
+ gap: 8
794
+ },
795
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
796
+ style: {
797
+ color: token.labelPrimary,
798
+ fontSize: 14
799
+ },
800
+ children: role.displayName || id
801
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
802
+ style: {
803
+ display: "flex",
804
+ alignItems: "center",
805
+ gap: 6
806
+ },
807
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
808
+ style: {
809
+ color: token.labelTertiary,
810
+ fontSize: 12,
811
+ fontVariantNumeric: "tabular-nums"
812
+ },
813
+ children: id
814
+ }), isDefault ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
815
+ style: {
816
+ color: token.accent,
817
+ fontSize: 12
818
+ },
819
+ children: t("defaultRoleBadge")
820
+ }) : null]
821
+ })]
822
+ }),
823
+ role.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
824
+ style: {
825
+ margin: 0,
826
+ color: token.labelSecondary,
827
+ fontSize: 13,
828
+ lineHeight: "18px"
829
+ },
830
+ children: role.description
831
+ }) : null,
832
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
833
+ style: {
834
+ display: "flex",
835
+ flexWrap: "wrap",
836
+ gap: 8
837
+ },
838
+ children: [
839
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metadata, {
840
+ label: t("provider"),
841
+ value: role.provider
842
+ }),
843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metadata, {
844
+ label: t("model"),
845
+ value: role.model
846
+ }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metadata, {
848
+ label: t("reasoningEffort"),
849
+ value: role.reasoningEffort
850
+ }),
851
+ role.persona ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metadata, {
852
+ label: t("persona"),
853
+ value: role.persona
854
+ }) : null
855
+ ]
856
+ }),
857
+ failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
858
+ style: {
859
+ color: token.danger,
860
+ fontSize: 12
861
+ },
862
+ children: failure
863
+ }) : null,
864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
865
+ style: {
866
+ display: "flex",
867
+ gap: 8
868
+ },
869
+ children: [
870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
871
+ style: ghostButtonStyle,
872
+ disabled: busy,
873
+ onClick: () => setEditing(true),
874
+ children: t("save")
875
+ }),
876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
877
+ style: ghostButtonStyle,
878
+ disabled: busy || isDefault,
879
+ onClick: () => (onSetDefault(), void 0),
880
+ children: t("setDefaultRole")
881
+ }),
882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
883
+ style: dangerButtonStyle,
884
+ disabled: busy,
885
+ onClick: remove,
886
+ children: t("deleteRole")
887
+ })
888
+ ]
889
+ })
890
+ ]
891
+ });
892
+ }
893
+ function Metadata({ label, value }) {
894
+ if (!value) return null;
895
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
896
+ style: {
897
+ color: token.labelSecondary,
898
+ fontSize: 12
899
+ },
900
+ children: [
901
+ label,
902
+ ": ",
903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
904
+ style: { color: token.labelPrimary },
905
+ children: value
906
+ })
907
+ ]
908
+ });
909
+ }
910
+ //#endregion
911
+ //#region src/client/SubagentOptionsSection.tsx
912
+ /**
913
+ * Subagent Director settings section (design section 9): the default-model row
914
+ * plus the role-template cards. The slot outlet erases the share boundary and
915
+ * delivers the inject face flat (PropsRuntime<'settings.section'> renderer is
916
+ * the DSH shell); this component guards for a not-yet-injected render and then
917
+ * renders its content column from the live snapshot.
918
+ *
919
+ * State lives in the page store (SubagentOptionsStore); every write travels as
920
+ * path ops through settings.mutate with an optimistic-revision lock, so the
921
+ * section only ever echoes the acknowledge-and-reload outcome. Failures return
922
+ * a localized message that the save/delete/restore controls surface inline.
923
+ */
924
+ /** Derive reasoning-effort options from the catalog for an exact provider+model. */
925
+ function effortsFor(groups, provider, model) {
926
+ if (!provider || !model) return [];
927
+ return (groups.find((g) => g.id === provider)?.models.find((m) => m.id === model))?.reasoning?.efforts ?? [];
928
+ }
929
+ /**
930
+ * Render the Subagent Director settings section content column.
931
+ * @param props - slot-delivered injected dependencies.
932
+ * @returns the section, or null while the shell has not injected yet.
933
+ */
934
+ function SubagentOptionsSection(props) {
935
+ const { controller, useSnapshot, t } = props;
936
+ if (controller === void 0 || useSnapshot === void 0 || t === void 0) return null;
937
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Loaded, { injected: {
938
+ controller,
939
+ useSnapshot,
940
+ t
941
+ } });
942
+ }
943
+ function Loaded({ injected }) {
944
+ const { controller, t } = injected;
945
+ const state = injected.useSnapshot((s) => s);
946
+ (0, react.useEffect)(() => {
947
+ if (state.status === "idle" && !state.loading) controller.load();
948
+ }, [state.status, state.loading]);
949
+ if (state.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
950
+ style: sectionWidth,
951
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
952
+ style: {
953
+ color: token.danger,
954
+ fontSize: 13
955
+ },
956
+ children: [
957
+ t("loadError"),
958
+ ": ",
959
+ state.error ?? ""
960
+ ]
961
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
962
+ style: ghostButtonStyle,
963
+ onClick: () => void controller.load(),
964
+ children: t("save")
965
+ })]
966
+ });
967
+ if (state.status !== "ready") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
968
+ style: sectionWidth,
969
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
970
+ style: {
971
+ color: token.labelSecondary,
972
+ fontSize: 13
973
+ },
974
+ children: t("sectionIntro")
975
+ })
976
+ });
977
+ const section = state.section;
978
+ const writable = state.writable;
979
+ const roles = section?.roles ?? {};
980
+ const entries = Object.entries(roles);
981
+ const groups = state.models;
982
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
983
+ style: sectionWidth,
984
+ children: [
985
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
986
+ style: {
987
+ margin: 0,
988
+ color: token.labelSecondary,
989
+ fontSize: 13,
990
+ lineHeight: "18px"
991
+ },
992
+ children: t("sectionIntro")
993
+ }),
994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultModelRow, {
995
+ controller,
996
+ groups,
997
+ writable,
998
+ current: {
999
+ provider: section?.defaultProvider,
1000
+ model: section?.defaultModel,
1001
+ reasoningEffort: section?.defaultReasoningEffort
1002
+ },
1003
+ t
1004
+ }),
1005
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RolesBlock, {
1006
+ controller,
1007
+ groups,
1008
+ writable,
1009
+ roles: entries,
1010
+ defaultRole: section?.defaultRole,
1011
+ t
1012
+ })
1013
+ ]
1014
+ });
1015
+ }
1016
+ /** The default-model row: provider → model → reasoning-effort cascade + restore. */
1017
+ function DefaultModelRow({ controller, groups, writable, current, t }) {
1018
+ const [draft, setDraft] = (0, react.useState)({
1019
+ provider: current.provider ?? "",
1020
+ model: current.model ?? "",
1021
+ reasoningEffort: current.reasoningEffort ?? ""
1022
+ });
1023
+ const [busy, setBusy] = (0, react.useState)(false);
1024
+ const [failure, setFailure] = (0, react.useState)(void 0);
1025
+ const [done, setDone] = (0, react.useState)(false);
1026
+ (0, react.useEffect)(() => {
1027
+ if (busy) return;
1028
+ setDraft({
1029
+ provider: current.provider ?? "",
1030
+ model: current.model ?? "",
1031
+ reasoningEffort: current.reasoningEffort ?? ""
1032
+ });
1033
+ }, [
1034
+ current.provider,
1035
+ current.model,
1036
+ current.reasoningEffort
1037
+ ]);
1038
+ const provider = draft.provider;
1039
+ const model = draft.model;
1040
+ const modelOptions = provider ? groups.find((g) => g.id === provider)?.models ?? [] : [];
1041
+ const effortOptions = effortsFor(groups, provider, model);
1042
+ const save = async () => {
1043
+ setBusy(true);
1044
+ setFailure(void 0);
1045
+ try {
1046
+ const message = await controller.setDefaultModel({
1047
+ provider: draft.provider || void 0,
1048
+ model: draft.model || void 0,
1049
+ reasoningEffort: draft.reasoningEffort || void 0
1050
+ });
1051
+ if (message !== void 0) {
1052
+ setFailure(message);
1053
+ return;
1054
+ }
1055
+ setDone(true);
1056
+ } finally {
1057
+ setBusy(false);
1058
+ }
1059
+ };
1060
+ const restore = async () => {
1061
+ setBusy(true);
1062
+ setFailure(void 0);
1063
+ try {
1064
+ const message = await controller.restoreDefaults();
1065
+ if (message !== void 0) {
1066
+ setFailure(message);
1067
+ return;
1068
+ }
1069
+ setDone(true);
1070
+ } finally {
1071
+ setBusy(false);
1072
+ }
1073
+ };
1074
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1075
+ style: cardStyle,
1076
+ children: [
1077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1078
+ style: {
1079
+ display: "flex",
1080
+ alignItems: "baseline",
1081
+ justifyContent: "space-between",
1082
+ gap: 8
1083
+ },
1084
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
1085
+ style: {
1086
+ color: token.labelPrimary,
1087
+ fontSize: 14
1088
+ },
1089
+ children: t("defaultsHeading")
1090
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1091
+ style: ghostButtonStyle,
1092
+ disabled: !writable || busy,
1093
+ onClick: () => void restore(),
1094
+ children: t("restoreDefaults")
1095
+ })]
1096
+ }),
1097
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1098
+ style: {
1099
+ margin: 0,
1100
+ color: token.labelSecondary,
1101
+ fontSize: 13,
1102
+ lineHeight: "18px"
1103
+ },
1104
+ children: t("defaultsHint")
1105
+ }),
1106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1107
+ style: {
1108
+ display: "grid",
1109
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
1110
+ gap: 8
1111
+ },
1112
+ children: [
1113
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1114
+ style: rowStyle,
1115
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1116
+ style: fieldLabelStyle,
1117
+ children: t("defaultProvider")
1118
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1119
+ style: selectStyle,
1120
+ value: draft.provider,
1121
+ disabled: !writable || groups.length === 0,
1122
+ onChange: (e) => setDraft((d) => ({
1123
+ provider: e.target.value,
1124
+ model: "",
1125
+ reasoningEffort: ""
1126
+ })),
1127
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1128
+ value: "",
1129
+ children: "—"
1130
+ }), groups.map((g) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1131
+ value: g.id,
1132
+ children: g.name
1133
+ }, g.id))]
1134
+ })]
1135
+ }),
1136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1137
+ style: rowStyle,
1138
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1139
+ style: fieldLabelStyle,
1140
+ children: t("defaultModel")
1141
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1142
+ style: selectStyle,
1143
+ value: draft.model,
1144
+ disabled: !writable || modelOptions.length === 0,
1145
+ onChange: (e) => setDraft((d) => ({
1146
+ ...d,
1147
+ model: e.target.value,
1148
+ reasoningEffort: ""
1149
+ })),
1150
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1151
+ value: "",
1152
+ children: "—"
1153
+ }), modelOptions.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1154
+ value: m.id,
1155
+ children: m.name
1156
+ }, m.id))]
1157
+ })]
1158
+ }),
1159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1160
+ style: rowStyle,
1161
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1162
+ style: fieldLabelStyle,
1163
+ children: t("defaultReasoningEffort")
1164
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1165
+ style: selectStyle,
1166
+ value: draft.reasoningEffort,
1167
+ disabled: !writable || effortOptions.length === 0,
1168
+ onChange: (e) => setDraft((d) => ({
1169
+ ...d,
1170
+ reasoningEffort: e.target.value
1171
+ })),
1172
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1173
+ value: "",
1174
+ children: "—"
1175
+ }), effortOptions.map((e) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1176
+ value: e.id,
1177
+ children: e.name
1178
+ }, e.id))]
1179
+ })]
1180
+ })
1181
+ ]
1182
+ }),
1183
+ failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1184
+ style: {
1185
+ color: token.danger,
1186
+ fontSize: 12
1187
+ },
1188
+ children: failure
1189
+ }) : null,
1190
+ done ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1191
+ style: {
1192
+ color: token.accent,
1193
+ fontSize: 12
1194
+ },
1195
+ children: t("restoreDone")
1196
+ }) : null,
1197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1198
+ style: {
1199
+ display: "flex",
1200
+ gap: 8
1201
+ },
1202
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1203
+ style: primaryButtonStyle,
1204
+ disabled: !writable || busy,
1205
+ onClick: () => void save(),
1206
+ children: t("save")
1207
+ })
1208
+ })
1209
+ ]
1210
+ });
1211
+ }
1212
+ /** The role-template roster: cards plus an inline add form. */
1213
+ function RolesBlock({ controller, groups, writable, roles, defaultRole, t }) {
1214
+ const [adding, setAdding] = (0, react.useState)(false);
1215
+ const [draft, setDraft] = (0, react.useState)({
1216
+ displayName: "",
1217
+ description: "",
1218
+ persona: "",
1219
+ provider: "",
1220
+ model: "",
1221
+ reasoningEffort: ""
1222
+ });
1223
+ const [busy, setBusy] = (0, react.useState)(false);
1224
+ const [failure, setFailure] = (0, react.useState)(void 0);
1225
+ const beginAdd = () => {
1226
+ setDraft({
1227
+ displayName: "",
1228
+ description: "",
1229
+ persona: "",
1230
+ provider: "",
1231
+ model: "",
1232
+ reasoningEffort: ""
1233
+ });
1234
+ setFailure(void 0);
1235
+ setAdding(true);
1236
+ };
1237
+ const saveAdd = async () => {
1238
+ setBusy(true);
1239
+ setFailure(void 0);
1240
+ try {
1241
+ const existing = new Set(roles.map(([id]) => id));
1242
+ const id = roleIdFromName(draft.displayName, existing);
1243
+ const message = await controller.addRole(id, draft);
1244
+ if (message !== void 0) {
1245
+ setFailure(message);
1246
+ return;
1247
+ }
1248
+ setAdding(false);
1249
+ } finally {
1250
+ setBusy(false);
1251
+ }
1252
+ };
1253
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1254
+ style: cardStyle,
1255
+ children: [
1256
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1257
+ style: {
1258
+ display: "flex",
1259
+ alignItems: "baseline",
1260
+ justifyContent: "space-between",
1261
+ gap: 8
1262
+ },
1263
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
1264
+ style: {
1265
+ color: token.labelPrimary,
1266
+ fontSize: 14
1267
+ },
1268
+ children: t("rolesHeading")
1269
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1270
+ style: ghostButtonStyle,
1271
+ disabled: !writable || adding,
1272
+ onClick: beginAdd,
1273
+ children: t("addRole")
1274
+ })]
1275
+ }),
1276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1277
+ style: {
1278
+ margin: 0,
1279
+ color: token.labelSecondary,
1280
+ fontSize: 13,
1281
+ lineHeight: "18px"
1282
+ },
1283
+ children: t("rolesHint")
1284
+ }),
1285
+ adding ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1286
+ style: {
1287
+ border: "1px dashed " + token.border,
1288
+ borderRadius: 10,
1289
+ padding: 12,
1290
+ display: "flex",
1291
+ flexDirection: "column",
1292
+ gap: 10
1293
+ },
1294
+ children: [
1295
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1296
+ style: rowStyle,
1297
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1298
+ style: fieldLabelStyle,
1299
+ children: t("roleDisplayName")
1300
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1301
+ style: textInputStyle,
1302
+ value: draft.displayName,
1303
+ placeholder: t("displayNamePlaceholder"),
1304
+ onChange: (e) => setDraft((d) => ({
1305
+ ...d,
1306
+ displayName: e.target.value
1307
+ }))
1308
+ })]
1309
+ }),
1310
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1311
+ style: rowStyle,
1312
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1313
+ style: fieldLabelStyle,
1314
+ children: t("roleDescription")
1315
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1316
+ style: textAreaStyle,
1317
+ value: draft.description,
1318
+ placeholder: t("descriptionPlaceholder"),
1319
+ onChange: (e) => setDraft((d) => ({
1320
+ ...d,
1321
+ description: e.target.value
1322
+ }))
1323
+ })]
1324
+ }),
1325
+ failure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1326
+ style: {
1327
+ color: token.danger,
1328
+ fontSize: 12
1329
+ },
1330
+ children: failure
1331
+ }) : null,
1332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1333
+ style: {
1334
+ display: "flex",
1335
+ gap: 8
1336
+ },
1337
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1338
+ style: primaryButtonStyle,
1339
+ disabled: !writable || busy,
1340
+ onClick: () => void saveAdd(),
1341
+ children: t("addRole")
1342
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1343
+ style: ghostButtonStyle,
1344
+ disabled: busy,
1345
+ onClick: () => setAdding(false),
1346
+ children: t("cancel")
1347
+ })]
1348
+ })
1349
+ ]
1350
+ }) : null,
1351
+ roles.length === 0 && !adding ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1352
+ style: {
1353
+ margin: 0,
1354
+ color: token.labelSecondary,
1355
+ fontSize: 13
1356
+ },
1357
+ children: t("emptyRoles")
1358
+ }) : roles.map(([id, role]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleCard, {
1359
+ id,
1360
+ role,
1361
+ isDefault: defaultRole === id,
1362
+ groups,
1363
+ t,
1364
+ onSave: (d) => controller.updateRole(id, role, d),
1365
+ onDelete: () => controller.removeRole(id),
1366
+ onSetDefault: () => controller.setDefaultRole(id)
1367
+ }, id))
1368
+ ]
1369
+ });
1370
+ }
1371
+ //#endregion
1372
+ //#region src/client/subagent-model.ts
1373
+ /**
1374
+ * Prefer the latest assistant message's reported provenance; fall back to its
1375
+ * requested config. Order walks the snapshot's node list from the tail so we
1376
+ * surface the most recent completed request, which is the meaningful one when
1377
+ * a subagent retried or only partially ran.
1378
+ */
1379
+ function latestSubagentModel(snapshot) {
1380
+ if (snapshot === null || typeof snapshot !== "object") return { found: false };
1381
+ const nodes = snapshot.nodes;
1382
+ if (!Array.isArray(nodes)) return { found: false };
1383
+ for (let i = nodes.length - 1; i >= 0; i -= 1) {
1384
+ const node = nodes[i];
1385
+ if (node === null || typeof node !== "object") continue;
1386
+ if (node.kind !== "assistant") continue;
1387
+ const ref = provenanceOf(node);
1388
+ if (ref !== null) return ref;
1389
+ }
1390
+ return { found: false };
1391
+ }
1392
+ /** Resolve the model identity off one assistant message (provenance first). */
1393
+ function provenanceOf(assistant) {
1394
+ const reported = assistant.provenance;
1395
+ if (reported !== null && typeof reported === "object" && typeof reported.provider === "string" && reported.provider !== "" && typeof reported.model === "string" && reported.model !== "") return {
1396
+ found: true,
1397
+ provider: reported.provider,
1398
+ model: reported.model
1399
+ };
1400
+ const requested = assistant.requestConfig;
1401
+ if (requested !== null && typeof requested === "object" && typeof requested.provider === "string" && requested.provider !== "" && typeof requested.model === "string" && requested.model !== "") return {
1402
+ found: true,
1403
+ provider: requested.provider,
1404
+ model: requested.model
1405
+ };
1406
+ return null;
1407
+ }
1408
+ /**
1409
+ * Whether this conversation is an addressed subagent (a catalog-discovered
1410
+ * child) — the surface where the official read-only composer shows and where
1411
+ * a model readout is most useful. A null `subagent` on an ordinary session
1412
+ * returns false even when nodes carry provenance.
1413
+ */
1414
+ function isAddressedSubagent(snapshot) {
1415
+ if (snapshot === null || snapshot === void 0) return false;
1416
+ const subagent = snapshot.subagent;
1417
+ return subagent !== null && subagent !== void 0;
1418
+ }
1419
+ /**
1420
+ * Compact provider/model label, e.g. "deepseek/deepseek-v4-flash". The model
1421
+ * id can already include a provider prefix; we do not hyphenate or re-quote
1422
+ * so the exact route stays readable in one line.
1423
+ */
1424
+ function formatModelRef(ref) {
1425
+ return `${ref.provider}/${ref.model}`;
1426
+ }
1427
+ //#endregion
1428
+ //#region src/client/SubagentModelDock.tsx
1429
+ /** Inline styling using the shared token surface (no CSS pipeline; M2 deviation). */
1430
+ const style = {
1431
+ root: {
1432
+ display: "flex",
1433
+ alignItems: "center",
1434
+ gap: 6,
1435
+ padding: "3px 16px",
1436
+ fontSize: 12,
1437
+ lineHeight: "16px",
1438
+ color: "var(--dsw-alias-label-tertiary)"
1439
+ },
1440
+ ref: {
1441
+ color: "var(--dsw-alias-label-secondary)",
1442
+ fontFamily: "var(--dsw-font-family-mono, monospace)"
1443
+ }
1444
+ };
1445
+ /** Render the provider/model readout for an addressed subagent, or nothing. */
1446
+ function SubagentModelDock({ session, t }) {
1447
+ if (!isAddressedSubagent(session)) return null;
1448
+ const lookup = latestSubagentModel(session);
1449
+ if (!lookup.found) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1450
+ style: style.root,
1451
+ role: "status",
1452
+ children: t("modelNotRecorded")
1453
+ });
1454
+ const ref = lookup;
1455
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1456
+ style: style.root,
1457
+ role: "status",
1458
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelRanOn") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1459
+ style: style.ref,
1460
+ title: t("modelRanOnTitle", { model: formatModelRef(ref) }),
1461
+ children: formatModelRef(ref)
1462
+ })]
1463
+ });
1464
+ }
1465
+ //#endregion
1466
+ //#region src/client/index.ts
1467
+ /** Dictionary namespace owned by Subagent Director (bilingual, typed). */
1468
+ const NS = "settings.subagentDirector";
1469
+ /** Refetch the page snapshot only after its first load. */
1470
+ function refreshIfLoaded(controller) {
1471
+ if (controller.store.getSnapshot().status === "idle") return;
1472
+ controller.load();
1473
+ }
1474
+ /** Services required by the settings registration (cordis fiber inject). */
1475
+ const inject = [
1476
+ "slots",
1477
+ "locale",
1478
+ "connection",
1479
+ "remote"
1480
+ ];
1481
+ /**
1482
+ * Register the Subagent Director section once the `settings.section`
1483
+ * declaration is on the ledger, wire its store to the connection, and keep it
1484
+ * fresh on every pushed invalidation (settings or provider topology).
1485
+ */
1486
+ function apply(ctx) {
1487
+ ctx.effect(() => ctx.locale.register(NS, {
1488
+ zh,
1489
+ en
1490
+ }), "subagent-director: copy dictionaries");
1491
+ const connection = ctx.get("connection");
1492
+ const t = ctx.locale.bind(NS);
1493
+ const controller = new SubagentOptionsStore({
1494
+ rpc: connection.rpc,
1495
+ llm: connection.api.llm,
1496
+ t
1497
+ });
1498
+ const useSnapshot = (0, _deepseek_ai_dsh_client_web_react.bindSnapshotSelector)(controller.store);
1499
+ const injected = () => ({
1500
+ controller,
1501
+ useSnapshot,
1502
+ api: connection.api,
1503
+ t
1504
+ });
1505
+ ctx.effect(() => {
1506
+ const refresh = () => refreshIfLoaded(controller);
1507
+ const disposers = [
1508
+ ctx.remote.$on("settings/document-updated", refresh),
1509
+ ctx.remote.$on("llm/adapters-updated", refresh),
1510
+ ctx.on("connection/reset", refresh)
1511
+ ];
1512
+ return () => {
1513
+ for (const dispose of disposers) dispose();
1514
+ };
1515
+ }, "subagent-director: pushed invalidations");
1516
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1517
+ name: "settings.section",
1518
+ id: "subagent-director",
1519
+ order: 20,
1520
+ label: () => t("nav"),
1521
+ locale: NS,
1522
+ inject: injected
1523
+ }, SubagentOptionsSection));
1524
+ ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
1525
+ name: "conversation.composer.dock",
1526
+ id: "subagent-director-model",
1527
+ order: 90,
1528
+ locale: NS
1529
+ }, SubagentModelDock));
1530
+ }
1531
+ //#endregion
1532
+ exports.NS = NS;
1533
+ exports.apply = apply;
1534
+ exports.en = en;
1535
+ exports.inject = inject;
1536
+ exports.refreshIfLoaded = refreshIfLoaded;
1537
+ exports.zh = zh;
1538
+
1539
+ return module.exports;
1540
+ }
1541
+ });