@weniatall/dsh-task-models 0.0.0-stage → 0.2.1

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,933 @@
1
+ (function () {
2
+ window.__ModuleLoader__.load({
3
+ id: "@weniatall/dsh-task-models",
4
+ factory: (require) => {
5
+ var module = { exports: {} };
6
+ var exports = module.exports;
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ const react = require("react");
9
+
10
+ const NS = "task-models";
11
+ const SETTINGS_RPC_CHANNEL = "/task-models-settings";
12
+ const SETTINGS_SLOT = "settings.section";
13
+ const NAV_STYLE_ID = "dsh-task-models-nav-styles";
14
+ const FALLBACKS = ["taskRole", "llm"];
15
+ const TASK_TEMPLATES = [
16
+ { id: "explore", name: "探索与检索", detail: "搜索代码、定位实现、阅读上下文、收集事实。", keywords: ["搜索", "查找", "定位", "阅读", "explore", "search", "find", "locate", "grep", "read"] },
17
+ { id: "implement", name: "实现与修改", detail: "编写或修改代码、修复缺陷、重构。", keywords: ["实现", "修改", "编写", "修复", "重构", "implement", "fix", "refactor", "code", "edit"] },
18
+ { id: "review", name: "审查与复核", detail: "代码审查、正确性检查、安全与质量复核。", keywords: ["审查", "复核", "检查", "评审", "review", "audit", "verify", "check"] },
19
+ { id: "test", name: "测试与验证", detail: "编写或运行测试、复现问题、验证行为。", keywords: ["测试", "验证", "复现", "test", "verify", "reproduce"] },
20
+ { id: "research", name: "调研与分析", detail: "方案对比、资料调研、架构分析、取舍评估。", keywords: ["调研", "研究", "分析", "对比", "架构", "research", "analyze", "compare", "investigate"] },
21
+ { id: "docs", name: "文档与说明", detail: "编写文档、注释、README、变更说明。", keywords: ["文档", "注释", "说明", "doc", "docs", "readme", "comment"] },
22
+ ];
23
+ const RESERVED_IDS = new Set(["default", "task", "general"]);
24
+ const DEFAULT_ROUTING = {
25
+ enabled: false,
26
+ fallback: "taskRole",
27
+ useJev: true,
28
+ respectExplicitChildModel: true,
29
+ classifier: { provider: "", model: "", reasoningEffort: "" },
30
+ };
31
+ const DEFAULT_JEV = {
32
+ enabled: false,
33
+ baseURL: "https://api.typesafe.ai",
34
+ model: "jev-latest",
35
+ minConfidence: 0.6,
36
+ timeoutMs: 3000,
37
+ maxStateChars: 8000,
38
+ };
39
+ const INTRO_TEXT = "为不同类型的子任务选择不同模型;未命中时保持当前路由。";
40
+ const css = `
41
+ .tm-page{display:flex;flex-direction:column;gap:14px;width:100%;max-width:820px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);color-scheme:light dark}
42
+ .tm-page h2,.tm-page h3,.tm-page p{margin:0}.tm-page h2{font:var(--dsw-font-l-20)}.tm-intro{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-s-14);line-height:20px}
43
+ .tm-list{display:flex;flex-direction:column;gap:10px}.tm-card{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid var(--dsw-alias-border-subtle,var(--dsw-alias-border-l1));border-radius:var(--dsw-radius-l,12px);background:var(--dsw-alias-background-base,var(--dsw-alias-bg-module-platform))}
44
+ .tm-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.tm-title{font:var(--dsw-font-m-16);font-weight:600}.tm-detail{margin-top:3px!important;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
45
+ .tm-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.tm-field{display:flex;flex-direction:column;gap:5px}.tm-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500}
46
+ .tm-select,.tm-input{box-sizing:border-box;width:100%;height:36px;padding:0 10px;border:1px solid var(--dsw-alias-border-default,var(--dsw-alias-border-l2));border-radius:8px;outline:none;background:var(--dsw-alias-background-base,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14)}
47
+ .tm-select:focus-visible,.tm-input:focus-visible{border-color:var(--dsw-alias-brand-primary,#4d6bfe);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 18%,transparent)}.tm-select:disabled,.tm-input:disabled{opacity:.55}
48
+ .tm-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:8px}.tm-button{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:0 14px;border:1px solid var(--dsw-alias-border-default,var(--dsw-alias-border-l2));border-radius:18px;background:transparent;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);cursor:pointer}.tm-button:disabled{cursor:default;opacity:.45}.tm-primary{border-color:transparent;background:var(--dsw-alias-brand-primary,#4d6bfe);color:#fff}.tm-danger{color:var(--dsw-alias-state-error-primary)}
49
+ .tm-feedback{padding:9px 11px;border-radius:8px;font-size:12px;line-height:18px}.tm-feedback.tm-error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#d84848) 10%,transparent);color:var(--dsw-alias-state-error-primary,#d84848)}.tm-status{min-height:18px;color:var(--dsw-alias-label-tertiary);font-size:12px}
50
+ .tm-check{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:13px}.tm-note{padding:9px 11px;border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 7%,transparent);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
51
+ .tm-grid-3{grid-template-columns:repeat(3,minmax(0,1fr))}.tm-field-actions{justify-content:flex-end}.tm-actions-left{justify-content:flex-start}
52
+ .tm-badge{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:999px;font-size:11px;font-weight:510;white-space:nowrap;flex:none}
53
+ .tm-badge-ok{color:var(--dsw-alias-state-success-primary,#22c55e);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 12%,transparent)}
54
+ .tm-badge-warn{color:var(--dsw-alias-state-warning-primary,#d98e00);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary,#d98e00) 12%,transparent)}
55
+ .tm-jev-status{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.tm-jev-status.tm-ok{color:var(--dsw-alias-state-success-primary,#22c55e)}.tm-jev-status.tm-warn{color:var(--dsw-alias-state-warning-primary,#d98e00)}
56
+ .tm-wf-root{width:100%;min-width:0;display:flex;flex-direction:column;gap:2px}
57
+ .tm-wf-header{box-sizing:border-box;width:100%;min-width:0;height:32px;padding:0 8px;border:0;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:6px;text-align:left;font:inherit;cursor:pointer}
58
+ .tm-wf-header:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}
59
+ .tm-wf-chevron{width:16px;flex:none;display:inline-flex;align-items:center;justify-content:center;color:inherit;font-size:11px}
60
+ .tm-wf-title{max-width:42%;flex:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);font-weight:510}
61
+ .tm-wf-summary{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);color:var(--dsw-alias-label-tertiary)}
62
+ .tm-wf-status{flex:none;white-space:nowrap;font-size:11px;font-weight:510;color:var(--dsw-alias-label-secondary)}
63
+ .tm-wf-status.tm-wf-running{color:var(--dsw-alias-state-business-primary,#4d6bfe)}.tm-wf-status.tm-wf-failed{color:var(--dsw-alias-state-error-primary,#d84848)}.tm-wf-status.tm-wf-completed{color:var(--dsw-alias-state-success-primary,#22c55e)}
64
+ .tm-wf-phase-list{display:flex;flex-direction:column;gap:4px;min-width:0;padding:4px 0 0 16px}
65
+ .tm-wf-phase{min-width:0}
66
+ .tm-wf-phase-header{box-sizing:border-box;width:100%;min-width:0;height:32px;padding:0;border:0;background:transparent;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:6px;text-align:left;font:inherit;cursor:pointer}
67
+ .tm-wf-phase-title{min-width:0;max-width:42%;flex:0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px)}
68
+ .tm-wf-phase-count{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);color:var(--dsw-alias-label-tertiary)}
69
+ .tm-wf-phase-status{flex:none;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);color:var(--dsw-alias-label-secondary);text-align:right}
70
+ .tm-wf-members{display:flex;flex-direction:column;gap:2px;min-width:0;padding:0 0 0 16px}
71
+ .tm-wf-member{box-sizing:border-box;width:100%;min-width:0;min-height:24px;display:flex;align-items:center;gap:8px;padding:0 0 0 2px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);text-align:left;font:inherit}
72
+ .tm-wf-member.tm-wf-clickable{cursor:pointer;border-radius:var(--dsw-radius-sm)}.tm-wf-member.tm-wf-clickable:hover .tm-wf-label{text-decoration:underline dotted;text-underline-offset:3px}
73
+ .tm-wf-dot{width:10px;height:10px;flex:none;border-radius:50%;border:1.5px solid var(--dsw-alias-label-tertiary);box-sizing:border-box}
74
+ .tm-wf-dot.tm-wf-running{border-color:var(--dsw-alias-state-business-primary,#4d6bfe);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4d6bfe) 20%,transparent)}
75
+ .tm-wf-dot.tm-wf-completed{border-color:var(--dsw-alias-state-success-primary,#22c55e);background:var(--dsw-alias-state-success-primary,#22c55e)}
76
+ .tm-wf-dot.tm-wf-failed{border-color:var(--dsw-alias-state-error-primary,#d84848);background:var(--dsw-alias-state-error-primary,#d84848)}
77
+ .tm-wf-dot.tm-wf-cancelled,.tm-wf-dot.tm-wf-interrupted{border-color:var(--dsw-alias-state-warning-primary,#d98e00)}
78
+ .tm-wf-label{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--dsh-content-font-size,14px);color:var(--dsw-alias-label-secondary);padding:0 2px}
79
+ .tm-wf-model{max-width:46%;flex:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:1px 6px;border-radius:999px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);background:color-mix(in srgb,var(--dsw-alias-label-secondary) 9%,transparent);font-family:var(--dsw-font-family-mono,monospace)}
80
+ .tm-wf-model.tm-wf-model-pending{opacity:.6;font-family:inherit}
81
+ .tm-wf-member-status{width:64px;flex:none;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);color:var(--dsw-alias-label-secondary)}
82
+ .tm-wf-empty{padding:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px)}
83
+ @media(max-width:640px){.tm-grid,.tm-grid-3{grid-template-columns:1fr}.tm-card-head{flex-direction:column}.tm-actions{justify-content:stretch}.tm-button{flex:1}.tm-wf-phase-list,.tm-wf-members{padding-left:12px}.tm-wf-model{max-width:38%}}
84
+ `;
85
+ const navCss = 'button:has([data-settings-nav-label="task-models"]) > svg:first-child{display:none}';
86
+
87
+ function modelKey(provider, model) { return JSON.stringify([provider, model]); }
88
+ function parseModelKey(value) {
89
+ if (!value) return null;
90
+ try {
91
+ const parsed = JSON.parse(value);
92
+ return Array.isArray(parsed) && parsed.length === 2 ? { provider: parsed[0], model: parsed[1] } : null;
93
+ } catch { return null; }
94
+ }
95
+ function normalizeId(value) { return String(value || "").trim().toLowerCase(); }
96
+ function splitKeywords(value) {
97
+ return String(value || "").split(/[,,]/u).map((entry) => entry.trim()).filter(Boolean);
98
+ }
99
+ function normalizeKeywords(value) {
100
+ const seen = new Set();
101
+ const out = [];
102
+ for (const entry of Array.isArray(value) ? value : splitKeywords(value)) {
103
+ if (typeof entry !== "string") continue;
104
+ const keyword = entry.trim();
105
+ if (keyword === "" || seen.has(keyword.toLowerCase())) continue;
106
+ seen.add(keyword.toLowerCase());
107
+ out.push(keyword);
108
+ }
109
+ return out;
110
+ }
111
+ function rowsFromGroups(groups) {
112
+ return groups.flatMap((group) => group.models.map((model) => ({ group, model })));
113
+ }
114
+ function taskTemplateOf(id) {
115
+ return TASK_TEMPLATES.find((template) => template.id === normalizeId(id));
116
+ }
117
+ function profileDefaults(id) {
118
+ const template = taskTemplateOf(id);
119
+ return template
120
+ ? { id: template.id, name: template.name, detail: template.detail, keywords: [...template.keywords], provider: "", model: "", reasoningEffort: "", enabled: true }
121
+ : { id: normalizeId(id), name: normalizeId(id), detail: "", keywords: [], provider: "", model: "", reasoningEffort: "", enabled: true };
122
+ }
123
+ function normalizeProfile(row) {
124
+ const id = normalizeId(row?.id);
125
+ const defaults = profileDefaults(id);
126
+ return {
127
+ ...defaults,
128
+ ...row,
129
+ id,
130
+ name: typeof row?.name === "string" ? row.name : defaults.name,
131
+ detail: typeof row?.detail === "string" ? row.detail : defaults.detail,
132
+ keywords: normalizeKeywords(row?.keywords ?? defaults.keywords),
133
+ provider: typeof row?.provider === "string" ? row.provider : "",
134
+ model: typeof row?.model === "string" ? row.model : "",
135
+ reasoningEffort: typeof row?.reasoningEffort === "string" ? row.reasoningEffort : "",
136
+ enabled: row?.enabled !== false,
137
+ };
138
+ }
139
+ function normalizeProfiles(rows) {
140
+ return (Array.isArray(rows) ? rows : []).map(normalizeProfile);
141
+ }
142
+ function profilesById(rows) {
143
+ return new Map((rows || []).map((row) => [normalizeId(row.id), row]));
144
+ }
145
+ function validateRouting(routing) {
146
+ if (!FALLBACKS.includes(routing?.fallback)) return "子任务回退方式无效";
147
+ const classifier = routing?.classifier || {};
148
+ if ((classifier.provider === "") !== (classifier.model === "")) return "轻量分类模型需要同时选择 Provider 与模型";
149
+ return "";
150
+ }
151
+ function validateProfiles(rows) {
152
+ const seen = new Set();
153
+ for (const row of rows || []) {
154
+ const id = normalizeId(row.id);
155
+ if (!/^[a-z][a-z0-9_-]*$/u.test(id)) return `任务类型 ID“${row.id}”格式无效`;
156
+ if (RESERVED_IDS.has(id)) return `任务类型 ID“${id}”与保留名冲突`;
157
+ if (seen.has(id)) return `任务类型 ID“${id}”重复`;
158
+ seen.add(id);
159
+ if (!row.provider || !row.model) return `任务类型“${id}”尚未选择模型`;
160
+ }
161
+ return "";
162
+ }
163
+ function normalizeJev(value) {
164
+ const source = value !== null && typeof value === "object" ? value : {};
165
+ return {
166
+ enabled: source.enabled === true,
167
+ baseURL: typeof source.baseURL === "string" ? source.baseURL : DEFAULT_JEV.baseURL,
168
+ model: typeof source.model === "string" ? source.model : DEFAULT_JEV.model,
169
+ minConfidence: Number.isFinite(Number(source.minConfidence)) ? Number(source.minConfidence) : DEFAULT_JEV.minConfidence,
170
+ timeoutMs: Number.isFinite(Number(source.timeoutMs)) ? Number(source.timeoutMs) : DEFAULT_JEV.timeoutMs,
171
+ maxStateChars: Number.isFinite(Number(source.maxStateChars)) ? Number(source.maxStateChars) : DEFAULT_JEV.maxStateChars,
172
+ };
173
+ }
174
+ function validateJev(jev) {
175
+ if (!(Number(jev?.minConfidence) >= 0 && Number(jev?.minConfidence) <= 1)) return "Jev 置信度阈值须在 0 到 1 之间";
176
+ if (!(Number(jev?.timeoutMs) >= 100 && Number(jev?.timeoutMs) <= 60000)) return "Jev 超时须在 100 到 60000 毫秒之间";
177
+ if (!(Number(jev?.maxStateChars) >= 500 && Number(jev?.maxStateChars) <= 100000)) return "Jev 上下文字符上限须在 500 到 100000 之间";
178
+ if (typeof jev?.baseURL !== "string" || jev.baseURL.trim() === "") return "Jev Base URL 不能为空";
179
+ try {
180
+ const url = new URL(jev.baseURL.trim());
181
+ if (url.protocol !== "http:" && url.protocol !== "https:") return "Jev Base URL 必须是 http(s)";
182
+ } catch {
183
+ return "Jev Base URL 无效";
184
+ }
185
+ if (typeof jev?.model !== "string" || jev.model.trim() === "") return "Jev 模型不能为空";
186
+ return "";
187
+ }
188
+ function clampNumber(value, fallback, min, max) {
189
+ const number = Number(value);
190
+ if (!Number.isFinite(number)) return fallback;
191
+ return Math.min(max, Math.max(min, number));
192
+ }
193
+ function statusMessage(status, writable, dirty) {
194
+ if (status === "loading") return "正在读取任务模型配置…";
195
+ if (!writable) return "当前设置文档为只读。";
196
+ if (dirty) return "有未保存的更改。";
197
+ return "";
198
+ }
199
+ function createSettingsRequest(connectionRpc, defaultApi) {
200
+ return async (method, payload = {}, api = defaultApi) => {
201
+ try {
202
+ const result = await connectionRpc.call(SETTINGS_RPC_CHANNEL, method, payload);
203
+ if (result && result.ok === true) return result.value;
204
+ throw new Error(result?.error?.message || "任务模型设置请求失败");
205
+ } catch (error) {
206
+ if (!/HTTP 403\b/u.test(error instanceof Error ? error.message : String(error)) || !api?.settings) throw error;
207
+ if (method === "describe") {
208
+ const desc = await api.settings.describe({});
209
+ if (!desc.result.ok) throw new Error(desc.result.error.message);
210
+ const entry = (desc.result.value.namespaces || []).find((entry) => entry.ns === NS);
211
+ return {
212
+ writable: desc.result.value.writable,
213
+ value: entry?.value || {},
214
+ revision: entry?.revision || 0,
215
+ };
216
+ }
217
+ if (method === "replace") {
218
+ const resp = await api.settings.replace({
219
+ ns: NS,
220
+ section: payload.section,
221
+ expectedRevision: payload.expectedRevision,
222
+ });
223
+ if (!resp.result.ok) throw new Error(resp.result.error.message);
224
+ return {
225
+ writable: true,
226
+ value: resp.result.value.value || payload.section,
227
+ revision: resp.result.value.revision || 0,
228
+ };
229
+ }
230
+ throw error;
231
+ }
232
+ };
233
+ }
234
+
235
+ function TaskProfileCard(props) {
236
+ const {
237
+ profileId, template, profile, modelRows, groups, saving, custom,
238
+ onChangeModel, onSetField, onRemove, onRename,
239
+ } = props;
240
+ const selected = profile?.provider && profile?.model
241
+ ? modelRows.find(({ group, model }) => group.id === profile.provider && model.id === profile.model)
242
+ : null;
243
+ const efforts = selected?.model?.reasoning?.efforts || [];
244
+ const title = custom ? (profile?.name || "自定义任务") : template?.name || profileId;
245
+ const detail = custom
246
+ ? "自定义任务类型;匹配关键词,或用 Jev/轻量模型按名称与描述判断。"
247
+ : template?.detail || "";
248
+ const modelOptions = groups.map((group) => react.createElement("optgroup", { label: group.name, key: group.id },
249
+ ...group.models.map((model) => react.createElement("option", {
250
+ key: model.id, value: modelKey(group.id, model.id),
251
+ }, model.name))));
252
+ const effortOptions = efforts.map((effort) => react.createElement("option", {
253
+ key: effort.id, value: effort.id,
254
+ }, effort.name));
255
+ const keywords = (profile?.keywords ?? template?.keywords ?? []).join(", ");
256
+ return react.createElement("section", { className: "tm-card", key: profileId },
257
+ react.createElement("div", { className: "tm-card-head" },
258
+ react.createElement("div", null,
259
+ react.createElement("h3", { className: "tm-title" }, title),
260
+ react.createElement("p", { className: "tm-detail" }, detail)),
261
+ custom ? react.createElement("button", {
262
+ className: "tm-button tm-danger", type: "button", disabled: saving, onClick: () => onRemove(profileId),
263
+ }, "删除") : null),
264
+ custom ? react.createElement("div", { className: "tm-grid" },
265
+ react.createElement("label", { className: "tm-field" },
266
+ react.createElement("span", { className: "tm-label" }, "任务类型 ID"),
267
+ react.createElement("input", {
268
+ className: "tm-input", value: profileId, disabled: saving,
269
+ onChange: (event) => onRename(profileId, event.target.value),
270
+ "aria-label": `${profileId} 任务类型 ID`,
271
+ })),
272
+ react.createElement("label", { className: "tm-field" },
273
+ react.createElement("span", { className: "tm-label" }, "显示名称"),
274
+ react.createElement("input", {
275
+ className: "tm-input", value: profile?.name ?? "", disabled: saving,
276
+ onChange: (event) => onSetField(profileId, { name: event.target.value }),
277
+ "aria-label": `${profileId} 显示名称`,
278
+ }))) : null,
279
+ custom ? react.createElement("label", { className: "tm-field" },
280
+ react.createElement("span", { className: "tm-label" }, "任务描述(供 Jev / 轻量模型判断)"),
281
+ react.createElement("input", {
282
+ className: "tm-input", value: profile?.detail ?? "", disabled: saving,
283
+ onChange: (event) => onSetField(profileId, { detail: event.target.value }),
284
+ "aria-label": `${profileId} 任务描述`,
285
+ })) : null,
286
+ react.createElement("div", { className: "tm-grid" },
287
+ react.createElement("label", { className: "tm-field" },
288
+ react.createElement("span", { className: "tm-label" }, "模型"),
289
+ react.createElement("select", {
290
+ className: "tm-select", value: profile?.provider && profile?.model ? modelKey(profile.provider, profile.model) : "", disabled: saving,
291
+ onChange: (event) => onChangeModel(profileId, event.target.value), "aria-label": `${profileId} 任务模型`,
292
+ },
293
+ react.createElement("option", { value: "" }, "不启用(保持当前路由)"),
294
+ ...modelOptions)),
295
+ react.createElement("label", { className: "tm-field" },
296
+ react.createElement("span", { className: "tm-label" }, "思考档位"),
297
+ react.createElement("select", {
298
+ className: "tm-select", value: profile?.reasoningEffort || "", disabled: !profile || efforts.length === 0 || saving,
299
+ onChange: (event) => onSetField(profileId, { reasoningEffort: event.target.value }), "aria-label": `${profileId} 任务思考档位`,
300
+ },
301
+ react.createElement("option", { value: "" }, "模型默认"),
302
+ ...effortOptions))),
303
+ react.createElement("label", { className: "tm-field" },
304
+ react.createElement("span", { className: "tm-label" }, "匹配关键词(逗号分隔;留空则只依赖 Jev/轻量模型)"),
305
+ react.createElement("input", {
306
+ className: "tm-input", value: keywords, disabled: !profile || saving,
307
+ onChange: (event) => onSetField(profileId, { keywords: splitKeywords(event.target.value) }),
308
+ "aria-label": `${profileId} 匹配关键词`,
309
+ })),
310
+ react.createElement("label", { className: "tm-check" },
311
+ react.createElement("input", {
312
+ type: "checkbox", checked: profile?.enabled !== false && profile !== undefined, disabled: !profile || saving,
313
+ onChange: (event) => onSetField(profileId, { enabled: event.target.checked }),
314
+ }),
315
+ "启用此任务类型"));
316
+ }
317
+
318
+ function apply(ctx) {
319
+ if (typeof document !== "undefined" && !document.getElementById(NAV_STYLE_ID)) {
320
+ const style = document.createElement("style");
321
+ style.id = NAV_STYLE_ID;
322
+ style.textContent = navCss;
323
+ document.head.appendChild(style);
324
+ }
325
+ const settingsRequest = createSettingsRequest(ctx.connection.rpc, ctx.connection.api);
326
+
327
+ const WORKFLOW_STYLE_ID = "dsh-task-models-styles";
328
+ if (typeof document !== "undefined" && !document.getElementById(WORKFLOW_STYLE_ID)) {
329
+ const style = document.createElement("style");
330
+ style.id = WORKFLOW_STYLE_ID;
331
+ style.textContent = css;
332
+ document.head.appendChild(style);
333
+ }
334
+
335
+ function statusText(status) {
336
+ return {
337
+ running: "运行中",
338
+ completed: "已完成",
339
+ failed: "失败",
340
+ cancelled: "已取消",
341
+ interrupted: "已中断",
342
+ }[status] || String(status || "");
343
+ }
344
+
345
+ function phaseStatusText(members) {
346
+ const counts = new Map();
347
+ for (const member of members) {
348
+ const status = member.status || "running";
349
+ counts.set(status, (counts.get(status) || 0) + 1);
350
+ }
351
+ return [...counts.entries()].map(([status, count]) => `${statusText(status)} ${count}`).join(" · ");
352
+ }
353
+
354
+ async function loadChildModels(childIds) {
355
+ if (!Array.isArray(childIds) || childIds.length === 0) return {};
356
+ const value = await settingsRequest("childModels", { childIds });
357
+ return value?.models && typeof value.models === "object" ? value.models : {};
358
+ }
359
+
360
+ function sameNavigable(next, previous) {
361
+ if (next === previous) return true;
362
+ if (!Array.isArray(next) || !Array.isArray(previous) || next.length !== previous.length) return false;
363
+ return next.every((entry, index) => entry.id === previous[index].id && entry.mode === previous[index].mode);
364
+ }
365
+
366
+ function navigableMembers(sessions, phases, parentId, statuses) {
367
+ const catalog = sessions?.projectionsBySession?.[parentId];
368
+ const navigable = [];
369
+ for (const phase of phases) {
370
+ for (const member of phase.members) {
371
+ const child = catalog?.values?.subagentCatalog?.find((entry) => entry.id === member.childId);
372
+ const childRunning = statuses?.get?.(child?.id)?.running ?? sessions?.byId?.[child?.id]?.running;
373
+ if (member.status === "running" && child !== undefined && childRunning === true
374
+ && (child.mode === "one-shot" || child.mode === "continuable")) {
375
+ navigable.push({ id: child.id, mode: child.mode });
376
+ }
377
+ }
378
+ }
379
+ return navigable;
380
+ }
381
+
382
+ /**
383
+ * Shadow the host workflow-run renderer at a lower slot priority so
384
+ * each member row can show the model that actually served its child
385
+ * request. The durable node and Definition stay owned by the host.
386
+ */
387
+ function WorkflowRunPanel(props) {
388
+ const { node, sessionId, openSession, loadModels, useSessions, useSessionStatus } = props;
389
+ const phases = Array.isArray(node?.data?.phases) ? node.data.phases : [];
390
+ const allMembers = phases.flatMap((phase) => (Array.isArray(phase.members) ? phase.members : []));
391
+ const [runOpen, setRunOpen] = react.useState(node?.data?.status !== "completed");
392
+ const [openPhases, setOpenPhases] = react.useState(() => new Set(
393
+ phases.filter((phase) => (phase.members || []).some((member) => member.status !== "completed")).map((phase) => phase.key),
394
+ ));
395
+ const [models, setModels] = react.useState({});
396
+ const statuses = typeof useSessionStatus === "function"
397
+ ? useSessionStatus((value) => value)
398
+ : new Map();
399
+ const navigable = typeof useSessions === "function"
400
+ ? useSessions((sessions) => navigableMembers(sessions, phases, sessionId, statuses), sameNavigable)
401
+ : [];
402
+ const childKey = allMembers
403
+ .map((member) => member.childId)
404
+ .filter((id) => typeof id === "string" && id !== "")
405
+ .join("\u0000");
406
+ const hasRunning = allMembers.some((member) => member.status === "running");
407
+
408
+ react.useEffect(() => {
409
+ const childIds = childKey === "" ? [] : childKey.split("\u0000");
410
+ if (childIds.length === 0 || typeof loadModels !== "function") return () => {};
411
+ let cancelled = false;
412
+ const load = async () => {
413
+ try {
414
+ const value = await loadModels(childIds);
415
+ if (!cancelled) setModels(value || {});
416
+ } catch {
417
+ // The model badge is additive; keep the last known values.
418
+ }
419
+ };
420
+ void load();
421
+ const timer = hasRunning ? setInterval(load, 2000) : undefined;
422
+ return () => {
423
+ cancelled = true;
424
+ if (timer !== undefined) clearInterval(timer);
425
+ };
426
+ }, [childKey, loadModels, hasRunning]);
427
+
428
+ const toggleRun = () => setRunOpen((value) => !value);
429
+ const togglePhase = (key) => setOpenPhases((current) => {
430
+ const next = new Set(current);
431
+ if (next.has(key)) next.delete(key);
432
+ else next.add(key);
433
+ return next;
434
+ });
435
+
436
+ if (phases.length === 0) {
437
+ return react.createElement("div", { className: "tm-wf-root" },
438
+ react.createElement("div", { className: "tm-wf-empty" }, "没有启动成员"));
439
+ }
440
+
441
+ return react.createElement("div", { className: "tm-wf-root" },
442
+ react.createElement("button", {
443
+ className: "tm-wf-header", type: "button", "aria-expanded": runOpen, onClick: toggleRun,
444
+ },
445
+ react.createElement("span", { className: "tm-wf-chevron" }, runOpen ? "▾" : "▸"),
446
+ react.createElement("span", { className: "tm-wf-title" }, node.data.name || "workflow"),
447
+ react.createElement("span", { className: "tm-wf-summary" }, `${allMembers.length} 个成员`),
448
+ react.createElement("span", { className: `tm-wf-status tm-wf-${node.data.status || "running"}` }, statusText(node.data.status || "running"))),
449
+ runOpen ? react.createElement("div", { className: "tm-wf-phase-list" },
450
+ ...phases.map((phase, phaseIndex) => {
451
+ const members = Array.isArray(phase.members) ? phase.members : [];
452
+ const phaseOpen = openPhases.has(phase.key);
453
+ return react.createElement("div", { className: "tm-wf-phase", key: phase.key ?? phaseIndex },
454
+ react.createElement("button", {
455
+ className: "tm-wf-phase-header", type: "button", "aria-expanded": phaseOpen,
456
+ onClick: () => togglePhase(phase.key),
457
+ },
458
+ react.createElement("span", { className: "tm-wf-chevron" }, phaseOpen ? "▾" : "▸"),
459
+ react.createElement("span", { className: "tm-wf-phase-title" }, phase.phase || "默认阶段"),
460
+ react.createElement("span", { className: "tm-wf-phase-count" }, `${members.length} 个成员`),
461
+ react.createElement("span", { className: "tm-wf-phase-status" }, phaseStatusText(members))),
462
+ phaseOpen ? react.createElement("div", { className: "tm-wf-members" },
463
+ ...members.map((member, memberIndex) => {
464
+ const info = models[member.childId];
465
+ const model = typeof info?.model === "string" ? info.model : "";
466
+ const pending = model === "" && member.status === "running";
467
+ const target = navigable.find((entry) => entry.id === member.childId);
468
+ const clickable = target !== undefined && typeof openSession === "function";
469
+ const content = [
470
+ react.createElement("span", { className: `tm-wf-dot tm-wf-${member.status || "running"}`, key: "dot" }),
471
+ react.createElement("span", { className: "tm-wf-label", key: "label", title: member.label || "" }, member.label || "空成员名"),
472
+ model !== "" || pending
473
+ ? react.createElement("span", {
474
+ className: `tm-wf-model ${pending ? "tm-wf-model-pending" : ""}`,
475
+ key: "model",
476
+ title: info
477
+ ? `${info.provider}/${info.model}${info.reasoningEffort ? ` · ${info.reasoningEffort}` : ""}`
478
+ : "等待子代理首次请求",
479
+ }, model || "模型…")
480
+ : null,
481
+ react.createElement("span", { className: "tm-wf-member-status", key: "status" }, statusText(member.status || "running")),
482
+ ].filter(Boolean);
483
+ return clickable
484
+ ? react.createElement("button", {
485
+ className: "tm-wf-member tm-wf-clickable", type: "button", key: member.seq ?? memberIndex,
486
+ "data-member-status": member.status,
487
+ onClick: () => openSession({
488
+ parentSessionId: sessionId,
489
+ childSessionId: member.childId,
490
+ mode: target.mode,
491
+ }),
492
+ }, ...content)
493
+ : react.createElement("div", {
494
+ className: "tm-wf-member", key: member.seq ?? memberIndex, "data-member-status": member.status,
495
+ }, ...content);
496
+ })) : null);
497
+ })) : null);
498
+ }
499
+
500
+ function TaskModelsSettings(props) {
501
+ const [status, setStatus] = react.useState("loading");
502
+ const [error, setError] = react.useState("");
503
+ const [writable, setWritable] = react.useState(false);
504
+ const [revision, setRevision] = react.useState(0);
505
+ const [groups, setGroups] = react.useState([]);
506
+ const [failures, setFailures] = react.useState([]);
507
+ const [routing, setRouting] = react.useState({ ...DEFAULT_ROUTING, classifier: { ...DEFAULT_ROUTING.classifier } });
508
+ const [profiles, setProfiles] = react.useState([]);
509
+ const [jev, setJev] = react.useState({ ...DEFAULT_JEV });
510
+ const [jevStatus, setJevStatus] = react.useState({ configured: false, model: "" });
511
+ const [jevKey, setJevKey] = react.useState("");
512
+ const [jevTesting, setJevTesting] = react.useState(false);
513
+ const [jevTest, setJevTest] = react.useState("");
514
+ const [saved, setSaved] = react.useState("");
515
+ const [saving, setSaving] = react.useState(false);
516
+ const dirtyRef = react.useRef(false);
517
+
518
+ const load = async (silent = false) => {
519
+ if (!silent) {
520
+ setStatus("loading");
521
+ setError("");
522
+ }
523
+ try {
524
+ const catalogPromise = ctx.remote?.session?.modelCatalog
525
+ ? ctx.remote.session.modelCatalog()
526
+ : props.api?.llm?.models({});
527
+ const [settingsView, modelsResponse] = await Promise.all([
528
+ settingsRequest("describe", {}, props.api), catalogPromise,
529
+ ]);
530
+ const modelsValue = modelsResponse.ok === true
531
+ ? modelsResponse.value
532
+ : modelsResponse.result?.ok === true ? modelsResponse.result.value : null;
533
+ if (!modelsValue) {
534
+ const detail = modelsResponse.ok === false
535
+ ? modelsResponse.error?.message
536
+ : modelsResponse.result?.error?.message;
537
+ throw new Error(detail || "模型目录请求失败");
538
+ }
539
+ const nextRouting = {
540
+ ...DEFAULT_ROUTING,
541
+ ...(settingsView.value?.routing || {}),
542
+ classifier: { ...DEFAULT_ROUTING.classifier, ...(settingsView.value?.routing?.classifier || {}) },
543
+ };
544
+ const nextProfiles = normalizeProfiles(settingsView.value?.profiles);
545
+ const nextJev = normalizeJev(settingsView.value?.jev);
546
+ setWritable(settingsView.writable);
547
+ setRevision(settingsView.revision);
548
+ setGroups(modelsValue.groups || []);
549
+ setFailures(modelsValue.failures || []);
550
+ if (!silent || !dirtyRef.current) {
551
+ setRouting(nextRouting);
552
+ setProfiles(nextProfiles);
553
+ setJev(nextJev);
554
+ setSaved(JSON.stringify({ routing: nextRouting, profiles: nextProfiles, jev: nextJev }));
555
+ }
556
+ settingsRequest("jevStatus", {}, props.api)
557
+ .then((value) => setJevStatus({
558
+ configured: value?.configured === true,
559
+ model: typeof value?.model === "string" ? value.model : "",
560
+ }))
561
+ .catch(() => setJevStatus({ configured: false, model: "" }));
562
+ setStatus("ready");
563
+ } catch (cause) {
564
+ if (!silent) {
565
+ setStatus("error");
566
+ setError(cause instanceof Error ? cause.message : String(cause));
567
+ }
568
+ }
569
+ };
570
+
571
+ react.useEffect(() => {
572
+ let active = true;
573
+ const reload = () => { if (active) void load(true); };
574
+ void load(false);
575
+ const stops = [
576
+ ctx.remote.$on("llm/adapters-updated", reload),
577
+ ctx.remote.$on("settings/document-updated", (ns) => { if (ns === NS) reload(); }),
578
+ ];
579
+ return () => { active = false; for (const stop of stops) stop(); };
580
+ }, []);
581
+
582
+ const byProfile = profilesById(profiles);
583
+ const customProfiles = profiles.filter((profile) => taskTemplateOf(profile.id) === undefined);
584
+ const modelRows = rowsFromGroups(groups);
585
+ const dirty = JSON.stringify({ routing, profiles, jev }) !== saved || jevKey.trim() !== "";
586
+ dirtyRef.current = dirty;
587
+ const validation = validateRouting(routing) || validateProfiles(profiles) || validateJev(jev);
588
+ const feedback = error || validation;
589
+ const statusText = statusMessage(status, writable, dirty);
590
+
591
+ const setProfile = (id, patch) => {
592
+ setProfiles((current) => {
593
+ const target = normalizeId(id);
594
+ const index = current.findIndex((entry) => normalizeId(entry.id) === target);
595
+ if (patch === null) return current.filter((entry) => normalizeId(entry.id) !== target);
596
+ const previous = index < 0 ? profileDefaults(id) : current[index];
597
+ const next = normalizeProfile({ ...previous, ...patch, id: previous.id ?? id });
598
+ if (index < 0) return [...current, next];
599
+ return current.map((entry, at) => at === index ? next : entry);
600
+ });
601
+ };
602
+
603
+ const changeProfileModel = (id, value) => {
604
+ const selected = parseModelKey(value);
605
+ if (!selected) { setProfile(id, null); return; }
606
+ const row = modelRows.find(({ group, model }) => group.id === selected.provider && model.id === selected.model);
607
+ setProfile(id, {
608
+ provider: selected.provider,
609
+ model: selected.model,
610
+ reasoningEffort: row?.model?.reasoning?.defaultEffort || "",
611
+ });
612
+ };
613
+
614
+ const addProfile = () => {
615
+ let suffix = 1;
616
+ while (byProfile.has(`custom-${suffix}`)) suffix++;
617
+ const id = `custom-${suffix}`;
618
+ setProfiles((current) => [...current, normalizeProfile({ id, name: `自定义任务 ${suffix}`, provider: "", model: "" })]);
619
+ };
620
+
621
+ const renameProfile = (oldId, nextValue) => {
622
+ const target = normalizeId(oldId);
623
+ setProfiles((current) => current.map((entry) => normalizeId(entry.id) === target
624
+ ? normalizeProfile({ ...entry, id: normalizeId(nextValue) })
625
+ : entry));
626
+ };
627
+
628
+ const setProfileField = (id, patch) => setProfile(id, patch);
629
+
630
+ const changeClassifier = (value) => {
631
+ const selected = parseModelKey(value);
632
+ if (!selected) {
633
+ setRouting((current) => ({ ...current, classifier: { provider: "", model: "", reasoningEffort: "" } }));
634
+ return;
635
+ }
636
+ const row = modelRows.find(({ group, model }) => group.id === selected.provider && model.id === selected.model);
637
+ setRouting((current) => ({
638
+ ...current,
639
+ classifier: {
640
+ provider: selected.provider,
641
+ model: selected.model,
642
+ reasoningEffort: row?.model?.reasoning?.defaultEffort || "",
643
+ },
644
+ }));
645
+ };
646
+
647
+ const refreshJevStatus = async () => {
648
+ try {
649
+ const value = await settingsRequest("jevStatus", {}, props.api);
650
+ setJevStatus({ configured: value?.configured === true, model: typeof value?.model === "string" ? value.model : "" });
651
+ } catch {
652
+ setJevStatus({ configured: false, model: "" });
653
+ }
654
+ };
655
+
656
+ const testJev = async () => {
657
+ setJevTesting(true);
658
+ setJevTest("");
659
+ try {
660
+ const payload = { ...(jevKey.trim() === "" ? {} : { apiKey: jevKey.trim() }), jev: normalizeJev(jev) };
661
+ const result = await settingsRequest("testJev", payload, props.api);
662
+ setJevTest(`✓ Jev 连接成功${result?.latencyMs === undefined ? "" : `(${result.latencyMs}ms)`}`);
663
+ setJevStatus((current) => ({ ...current, configured: true }));
664
+ } catch (cause) {
665
+ setJevTest(`Jev 测试失败:${cause instanceof Error ? cause.message : String(cause)}`);
666
+ } finally {
667
+ setJevTesting(false);
668
+ }
669
+ };
670
+
671
+ const save = async () => {
672
+ if (validation || !dirty || !writable) return;
673
+ setSaving(true);
674
+ setError("");
675
+ try {
676
+ if (jevKey.trim() !== "") {
677
+ await settingsRequest("saveJevKey", { apiKey: jevKey.trim() }, props.api);
678
+ setJevKey("");
679
+ }
680
+ const normalizedProfiles = profiles.map((row) => ({
681
+ id: normalizeId(row.id),
682
+ name: row.name || "",
683
+ detail: row.detail || "",
684
+ keywords: normalizeKeywords(row.keywords),
685
+ provider: row.provider,
686
+ model: row.model,
687
+ reasoningEffort: row.reasoningEffort || "",
688
+ enabled: row.enabled !== false,
689
+ }));
690
+ await settingsRequest("replace", {
691
+ section: {
692
+ routing: {
693
+ ...routing,
694
+ classifier: { ...routing.classifier },
695
+ },
696
+ profiles: normalizedProfiles,
697
+ jev: {
698
+ enabled: jev.enabled === true,
699
+ baseURL: jev.baseURL,
700
+ model: jev.model,
701
+ minConfidence: clampNumber(jev.minConfidence, DEFAULT_JEV.minConfidence, 0, 1),
702
+ timeoutMs: clampNumber(jev.timeoutMs, DEFAULT_JEV.timeoutMs, 100, 60000),
703
+ maxStateChars: clampNumber(jev.maxStateChars, DEFAULT_JEV.maxStateChars, 500, 100000),
704
+ },
705
+ },
706
+ expectedRevision: revision,
707
+ }, props.api);
708
+ await load(false);
709
+ } catch (cause) {
710
+ setError(cause instanceof Error ? cause.message : String(cause));
711
+ } finally {
712
+ setSaving(false);
713
+ }
714
+ };
715
+
716
+ const jevStatusText = jevStatus.configured
717
+ ? ""
718
+ : "尚未配置 API Key";
719
+
720
+ const classifier = routing.classifier || { provider: "", model: "", reasoningEffort: "" };
721
+ const classifierRows = modelRows.find(({ group, model }) => group.id === classifier.provider && model.id === classifier.model);
722
+ const classifierEfforts = classifierRows?.model?.reasoning?.efforts || [];
723
+
724
+ return react.createElement("div", { className: "tm-page" },
725
+ react.createElement("style", null, css),
726
+ react.createElement("h2", null, "任务模型"),
727
+ react.createElement("p", { className: "tm-intro" }, INTRO_TEXT),
728
+ feedback ? react.createElement("div", { className: "tm-feedback tm-error", role: "alert" }, feedback) : null,
729
+ react.createElement("section", { className: "tm-card" },
730
+ react.createElement("div", null,
731
+ react.createElement("h3", { className: "tm-title" }, "子任务路由"),
732
+ react.createElement("p", { className: "tm-detail" }, "对所有会话委派的子任务生效;不要求任何 Agent Preset,也不会改写主会话模型。")),
733
+ react.createElement("label", { className: "tm-check" },
734
+ react.createElement("input", {
735
+ type: "checkbox", checked: routing.enabled, disabled: saving,
736
+ onChange: (event) => setRouting((current) => ({ ...current, enabled: event.target.checked })),
737
+ }),
738
+ "启用按任务类型路由子任务"),
739
+ react.createElement("div", { className: "tm-grid" },
740
+ react.createElement("label", { className: "tm-field" },
741
+ react.createElement("span", { className: "tm-label" }, "规则未命中时"),
742
+ react.createElement("select", {
743
+ className: "tm-select", value: routing.fallback, disabled: saving,
744
+ onChange: (event) => setRouting((current) => ({ ...current, fallback: event.target.value })),
745
+ },
746
+ react.createElement("option", { value: "taskRole" }, "保持当前路由"),
747
+ react.createElement("option", { value: "llm" }, "用轻量模型分类"))),
748
+ react.createElement("div", { className: "tm-field" },
749
+ react.createElement("span", { className: "tm-label" }, "决策选项"),
750
+ react.createElement("label", { className: "tm-check" },
751
+ react.createElement("input", {
752
+ type: "checkbox", checked: routing.useJev, disabled: saving,
753
+ onChange: (event) => setRouting((current) => ({ ...current, useJev: event.target.checked })),
754
+ }),
755
+ "规则未命中时优先问 Jev"),
756
+ react.createElement("label", { className: "tm-check" },
757
+ react.createElement("input", {
758
+ type: "checkbox", checked: routing.respectExplicitChildModel, disabled: saving,
759
+ onChange: (event) => setRouting((current) => ({ ...current, respectExplicitChildModel: event.target.checked })),
760
+ }),
761
+ "尊重子任务显式选择的模型"))),
762
+ react.createElement("p", { className: "tm-note" }, "任务类型按子代理首次请求决定一次,后续请求沿用同一模型。规则并列时不会按配置顺序随机选择。")),
763
+ react.createElement("div", { className: "tm-card" },
764
+ react.createElement("div", null,
765
+ react.createElement("h3", { className: "tm-title" }, "轻量分类模型(可选)"),
766
+ react.createElement("p", { className: "tm-detail" }, "只有回退方式选择“轻量模型”时才使用;留空则回退保持当前路由。")),
767
+ react.createElement("div", { className: "tm-grid" },
768
+ react.createElement("label", { className: "tm-field" },
769
+ react.createElement("span", { className: "tm-label" }, "分类模型"),
770
+ react.createElement("select", {
771
+ className: "tm-select", value: classifier.provider && classifier.model ? modelKey(classifier.provider, classifier.model) : "", disabled: saving,
772
+ onChange: (event) => changeClassifier(event.target.value),
773
+ },
774
+ react.createElement("option", { value: "" }, "不配置"),
775
+ ...groups.map((group) => react.createElement("optgroup", { label: group.name, key: group.id },
776
+ ...group.models.map((model) => react.createElement("option", { key: model.id, value: modelKey(group.id, model.id) }, model.name)))))),
777
+ react.createElement("label", { className: "tm-field" },
778
+ react.createElement("span", { className: "tm-label" }, "分类思考档位"),
779
+ react.createElement("select", {
780
+ className: "tm-select", value: classifier.reasoningEffort || "", disabled: classifierEfforts.length === 0 || saving,
781
+ onChange: (event) => setRouting((current) => ({
782
+ ...current,
783
+ classifier: { ...current.classifier, reasoningEffort: event.target.value },
784
+ })),
785
+ },
786
+ react.createElement("option", { value: "" }, "模型默认"),
787
+ ...classifierEfforts.map((effort) => react.createElement("option", { key: effort.id, value: effort.id }, effort.name)))))),
788
+ react.createElement("div", { className: "tm-list" },
789
+ ...TASK_TEMPLATES.map((template) => react.createElement(TaskProfileCard, {
790
+ key: template.id,
791
+ profileId: template.id,
792
+ template,
793
+ profile: byProfile.get(template.id),
794
+ modelRows,
795
+ groups,
796
+ saving,
797
+ custom: false,
798
+ onChangeModel: changeProfileModel,
799
+ onSetField: setProfileField,
800
+ onRemove: setProfile,
801
+ onRename: renameProfile,
802
+ })),
803
+ ...customProfiles.map((profile) => react.createElement(TaskProfileCard, {
804
+ key: profile.id,
805
+ profileId: profile.id,
806
+ template: undefined,
807
+ profile,
808
+ modelRows,
809
+ groups,
810
+ saving,
811
+ custom: true,
812
+ onChangeModel: changeProfileModel,
813
+ onSetField: setProfileField,
814
+ onRemove: setProfile,
815
+ onRename: renameProfile,
816
+ }))),
817
+ react.createElement("div", { className: "tm-actions" },
818
+ react.createElement("button", { className: "tm-button", type: "button", disabled: saving || !writable, onClick: addProfile }, "添加任务类型")),
819
+ react.createElement("section", { className: "tm-card" },
820
+ react.createElement("div", { className: "tm-card-head" },
821
+ react.createElement("div", null,
822
+ react.createElement("h3", { className: "tm-title" }, "Jev 决策"),
823
+ react.createElement("p", { className: "tm-detail" }, "可选。规则未命中时由 TypeSafe Jev 在已配置任务类型中选择。")),
824
+ jevStatus.configured
825
+ ? react.createElement("span", { className: "tm-badge tm-badge-ok" }, "已连接")
826
+ : react.createElement("span", { className: "tm-badge tm-badge-warn" }, "未配置")),
827
+ react.createElement("label", { className: "tm-check" },
828
+ react.createElement("input", {
829
+ type: "checkbox", checked: jev.enabled, disabled: saving,
830
+ onChange: (event) => setJev((current) => ({ ...current, enabled: event.target.checked })),
831
+ }),
832
+ "启用 Jev 决策"),
833
+ react.createElement("div", { className: "tm-grid" },
834
+ react.createElement("label", { className: "tm-field" },
835
+ react.createElement("span", { className: "tm-label" }, "API Key"),
836
+ react.createElement("input", {
837
+ className: "tm-input", type: "password", value: jevKey, disabled: saving,
838
+ placeholder: jevStatus.configured ? "已保存,留空不修改" : "ts-...",
839
+ onChange: (event) => setJevKey(event.target.value),
840
+ })),
841
+ react.createElement("label", { className: "tm-field" },
842
+ react.createElement("span", { className: "tm-label" }, "Base URL"),
843
+ react.createElement("input", {
844
+ className: "tm-input", value: jev.baseURL, disabled: saving,
845
+ onChange: (event) => setJev((current) => ({ ...current, baseURL: event.target.value })),
846
+ }))),
847
+ react.createElement("div", { className: "tm-grid tm-grid-3" },
848
+ react.createElement("label", { className: "tm-field" },
849
+ react.createElement("span", { className: "tm-label" }, "模型"),
850
+ react.createElement("input", {
851
+ className: "tm-input", value: jev.model, disabled: saving,
852
+ onChange: (event) => setJev((current) => ({ ...current, model: event.target.value })),
853
+ })),
854
+ react.createElement("label", { className: "tm-field" },
855
+ react.createElement("span", { className: "tm-label" }, "最低置信度"),
856
+ react.createElement("input", {
857
+ className: "tm-input", type: "number", min: 0, max: 1, step: 0.05, value: jev.minConfidence, disabled: saving,
858
+ onChange: (event) => setJev((current) => ({ ...current, minConfidence: clampNumber(event.target.value, DEFAULT_JEV.minConfidence, 0, 1) })),
859
+ })),
860
+ react.createElement("label", { className: "tm-field" },
861
+ react.createElement("span", { className: "tm-label" }, "超时(毫秒)"),
862
+ react.createElement("input", {
863
+ className: "tm-input", type: "number", min: 100, max: 60000, step: 100, value: jev.timeoutMs, disabled: saving,
864
+ onChange: (event) => setJev((current) => ({ ...current, timeoutMs: clampNumber(event.target.value, DEFAULT_JEV.timeoutMs, 100, 60000) })),
865
+ }))),
866
+ react.createElement("div", { className: "tm-grid" },
867
+ react.createElement("label", { className: "tm-field" },
868
+ react.createElement("span", { className: "tm-label" }, "发送上下文字符上限"),
869
+ react.createElement("input", {
870
+ className: "tm-input", type: "number", min: 500, max: 100000, step: 500, value: jev.maxStateChars, disabled: saving,
871
+ onChange: (event) => setJev((current) => ({ ...current, maxStateChars: clampNumber(event.target.value, DEFAULT_JEV.maxStateChars, 500, 100000) })),
872
+ })),
873
+ react.createElement("div", { className: "tm-field tm-field-actions" },
874
+ react.createElement("span", { className: "tm-label" }, "连接"),
875
+ react.createElement("div", { className: "tm-actions tm-actions-left" },
876
+ react.createElement("button", { className: "tm-button", type: "button", disabled: saving || jevTesting, onClick: refreshJevStatus }, "刷新"),
877
+ react.createElement("button", { className: "tm-button", type: "button", disabled: saving || jevTesting, onClick: testJev }, jevTesting ? "测试中…" : "测试 Jev")))),
878
+ jevTest ? react.createElement("p", { className: "tm-jev-status" }, jevTest) : null,
879
+ jevStatusText ? react.createElement("p", { className: "tm-jev-status tm-warn" }, jevStatusText) : null),
880
+ react.createElement("div", { className: "tm-actions" },
881
+ react.createElement("button", { className: "tm-button", type: "button", disabled: saving || !dirty, onClick: () => load(false) }, "撤销"),
882
+ react.createElement("button", {
883
+ className: "tm-button tm-primary", type: "button",
884
+ disabled: saving || !writable || !dirty || Boolean(validation), onClick: save,
885
+ }, saving ? "保存中…" : "保存")),
886
+ statusText ? react.createElement("p", { className: "tm-status", role: "status", "aria-live": "polite" }, statusText) : null);
887
+ }
888
+
889
+ const IconTaskBranch = ({ size = 16 }) => react.createElement("svg", {
890
+ width: size, height: size, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", stroke: "currentColor", strokeWidth: 1.3,
891
+ },
892
+ react.createElement("path", { d: "M1 8h4.7c.7 0 1.1 0 1.4-.1.3-.1.6-.2.9-.4.3-.2.6-.4 1.1-.9l1.9-1.9" }),
893
+ react.createElement("circle", { cx: 12.5, cy: 3.7, r: 1.6 }),
894
+ react.createElement("path", { d: "M7.6 7.8l1.4 1.4c.5.5.8.8 1.1.9.3.2.6.3.9.4.3.1.7.1 1.4.1h1.6" }),
895
+ react.createElement("circle", { cx: 12.5, cy: 12.7, r: 1.6 }));
896
+
897
+ const label = () => react.createElement("span", {
898
+ "data-settings-nav-label": "task-models",
899
+ style: { display: "inline-flex", alignItems: "center", gap: 8 },
900
+ },
901
+ react.createElement(IconTaskBranch, { size: 16 }),
902
+ react.createElement("span", null, "任务模型"));
903
+ ctx.slots.inject(SETTINGS_SLOT, () => ctx.slots.register({
904
+ name: SETTINGS_SLOT, id: "task-models", order: 25, label,
905
+ inject: () => ({ api: ctx.connection.api }),
906
+ }, TaskModelsSettings));
907
+
908
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
909
+ name: "conversation.chat.node",
910
+ key: "workflow-run",
911
+ priority: -1,
912
+ inject: () => ({
913
+ loadModels: loadChildModels,
914
+ openSession: (target) => {
915
+ if (typeof ctx.uiWorkspace?.openSession === "function") ctx.uiWorkspace.openSession(target);
916
+ },
917
+ }),
918
+ }, WorkflowRunPanel));
919
+ }
920
+
921
+ exports.apply = apply;
922
+ exports.inject = ["slots", "connection", "remote", "remote.session", "uiWorkspace"];
923
+ exports.internals = {
924
+ NS, SETTINGS_RPC_CHANNEL, SETTINGS_SLOT, NAV_STYLE_ID, navCss, FALLBACKS, TASK_TEMPLATES,
925
+ RESERVED_IDS, DEFAULT_ROUTING, DEFAULT_JEV, INTRO_TEXT, modelKey, parseModelKey, normalizeId,
926
+ splitKeywords, normalizeKeywords, rowsFromGroups, taskTemplateOf, profileDefaults, normalizeProfile,
927
+ normalizeProfiles, profilesById, validateRouting, validateProfiles, normalizeJev, validateJev, clampNumber,
928
+ statusMessage, createSettingsRequest, TaskProfileCard,
929
+ };
930
+ return module.exports;
931
+ },
932
+ });
933
+ })();