@weniatall/dsh-task-models 0.2.1 → 0.3.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.
package/lib/client.js CHANGED
@@ -26,6 +26,7 @@
26
26
  fallback: "taskRole",
27
27
  useJev: true,
28
28
  respectExplicitChildModel: true,
29
+ decisionTimeoutMs: 5000,
29
30
  classifier: { provider: "", model: "", reasoningEffort: "" },
30
31
  };
31
32
  const DEFAULT_JEV = {
@@ -46,7 +47,10 @@
46
47
  .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
48
  .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
49
  .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-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-feedback.tm-warn{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary,#d98e00) 12%,transparent);color:var(--dsw-alias-state-warning-primary,#d98e00);display:flex;align-items:center;justify-content:space-between;gap:8px}.tm-status{min-height:18px;color:var(--dsw-alias-label-tertiary);font-size:12px}
51
+ .tm-preview-result{padding:10px 12px;border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-label-secondary) 8%,transparent);font-size:12px;line-height:20px;display:flex;flex-direction:column;gap:4px}
52
+ .tm-textarea{box-sizing:border-box;width:100%;min-height:72px;padding:8px 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);resize:vertical}
53
+ .tm-textarea: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-textarea:disabled{opacity:.55}
50
54
  .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
55
  .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
56
  .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}
@@ -144,10 +148,173 @@
144
148
  }
145
149
  function validateRouting(routing) {
146
150
  if (!FALLBACKS.includes(routing?.fallback)) return "子任务回退方式无效";
151
+ if (routing?.decisionTimeoutMs !== undefined && routing?.decisionTimeoutMs !== null) {
152
+ const ms = Number(routing.decisionTimeoutMs);
153
+ if (!Number.isInteger(ms) || ms < 100 || ms > 60000) return "决策总超时须在 100 到 60000 毫秒之间";
154
+ }
147
155
  const classifier = routing?.classifier || {};
148
156
  if ((classifier.provider === "") !== (classifier.model === "")) return "轻量分类模型需要同时选择 Provider 与模型";
149
157
  return "";
150
158
  }
159
+ function buildDraftSection(draft) {
160
+ const routing = draft?.routing;
161
+ const profiles = draft?.profiles;
162
+ const jev = draft?.jev;
163
+ const normalizedProfiles = (Array.isArray(profiles) ? profiles : []).map((row) => ({
164
+ id: normalizeId(row.id),
165
+ name: row.name || "",
166
+ detail: row.detail || "",
167
+ keywords: normalizeKeywords(row.keywords),
168
+ provider: row.provider,
169
+ model: row.model,
170
+ reasoningEffort: row.reasoningEffort || "",
171
+ enabled: row.enabled !== false,
172
+ }));
173
+ const normJev = normalizeJev(jev);
174
+ return {
175
+ routing: {
176
+ ...DEFAULT_ROUTING,
177
+ ...(routing || {}),
178
+ decisionTimeoutMs: clampNumber(Math.round(Number(routing?.decisionTimeoutMs) || DEFAULT_ROUTING.decisionTimeoutMs), DEFAULT_ROUTING.decisionTimeoutMs, 100, 60000),
179
+ classifier: { ...DEFAULT_ROUTING.classifier, ...(routing?.classifier || {}) },
180
+ },
181
+ profiles: normalizedProfiles,
182
+ jev: {
183
+ enabled: normJev.enabled === true,
184
+ baseURL: normJev.baseURL,
185
+ model: normJev.model,
186
+ minConfidence: clampNumber(normJev.minConfidence, DEFAULT_JEV.minConfidence, 0, 1),
187
+ timeoutMs: clampNumber(normJev.timeoutMs, DEFAULT_JEV.timeoutMs, 100, 60000),
188
+ maxStateChars: clampNumber(normJev.maxStateChars, DEFAULT_JEV.maxStateChars, 500, 100000),
189
+ },
190
+ };
191
+ }
192
+ function reconcileSettings({
193
+ currentRevision,
194
+ currentWritable,
195
+ remoteRevision,
196
+ remoteWritable,
197
+ silent,
198
+ dirty,
199
+ }) {
200
+ if (silent && dirty) {
201
+ const conflict = remoteRevision !== currentRevision;
202
+ return {
203
+ conflict,
204
+ revision: currentRevision,
205
+ writable: currentWritable,
206
+ shouldApplyRemote: false,
207
+ };
208
+ }
209
+ return {
210
+ conflict: false,
211
+ revision: remoteRevision,
212
+ writable: remoteWritable,
213
+ shouldApplyRemote: !silent || !dirty,
214
+ };
215
+ }
216
+ function formatRoutingDetail(reason) {
217
+ const REASONS = {
218
+ "rules-explicit-label": "规则匹配(显式标签)",
219
+ "rules-keywords": "规则匹配(关键词)",
220
+ "rules-unmatched": "规则未命中",
221
+ "jev-selected": "Jev 决策",
222
+ "llm-selected": "轻量模型分类",
223
+ "no-match": "无规则命中",
224
+ "rules-tie": "规则冲突平局",
225
+ "classifier-unconfigured": "分类模型未配置",
226
+ "jev-low-confidence": "Jev 置信度不足",
227
+ "jev-invalid-response": "Jev 响应无效",
228
+ "jev-unavailable": "Jev 服务不可用",
229
+ "classifier-failed": "轻量模型分类失败",
230
+ "decision-timeout": "决策超时",
231
+ "route-unusable": "目标路由不可用",
232
+ "explicit-child-model": "子代理指定模型",
233
+ "routing-disabled": "任务路由已禁用",
234
+ "no-profiles": "未配置任务模型",
235
+ "internal-role": "内部角色",
236
+ "plan-mode": "计划模式",
237
+ "existing-request": "已有会话延续",
238
+ "no-task": "无任务内容",
239
+ };
240
+ return (reason && REASONS[reason]) || (reason ? String(reason) : "原因未知");
241
+ }
242
+ function formatMemberTitle({ info, detail, pending }) {
243
+ const effective = detail?.effectiveRoute || info;
244
+ const hasEffectiveModel = Boolean(effective?.model);
245
+ if (pending && !hasEffectiveModel && !detail) return "等待子代理首次请求";
246
+ const parts = [];
247
+ if (hasEffectiveModel) {
248
+ const modelText = effective?.provider && effective?.model
249
+ ? `${effective.provider}/${effective.model}${effective.reasoningEffort ? ` · ${effective.reasoningEffort}` : ""}`
250
+ : (effective?.model || "");
251
+ if (modelText) parts.push(modelText);
252
+ } else if (detail?.selectedRoute) {
253
+ if (detail.status === "adopted") {
254
+ parts.push(`待请求头确认 (预选: ${detail.selectedRoute.provider}/${detail.selectedRoute.model})`);
255
+ } else {
256
+ parts.push(`未采用候选: ${detail.selectedRoute.provider}/${detail.selectedRoute.model}`);
257
+ }
258
+ } else if (pending) {
259
+ parts.push("等待子代理首次请求");
260
+ }
261
+ if (detail) {
262
+ const profile = detail.profileName || detail.profileId;
263
+ if (profile) parts.push(`类别: ${profile}`);
264
+ if (detail.source) {
265
+ const sourceMap = { rules: "规则", jev: "Jev", llm: "轻量模型", current: "保持当前" };
266
+ parts.push(`来源: ${sourceMap[detail.source] || detail.source}`);
267
+ }
268
+ if (typeof detail.confidence === "number") {
269
+ parts.push(`置信度: ${Math.round(detail.confidence * 100)}%`);
270
+ }
271
+ if (typeof detail.durationMs === "number") {
272
+ parts.push(`耗时: ${detail.durationMs}ms`);
273
+ }
274
+ if (detail.reason) {
275
+ parts.push(`原因: ${formatRoutingDetail(detail.reason)}`);
276
+ }
277
+ } else if (hasEffectiveModel) {
278
+ parts.push("原因未知");
279
+ }
280
+ return parts.join(" · ");
281
+ }
282
+ function formatNextStep(next) {
283
+ const MAP = {
284
+ rules: "规则命中",
285
+ jev: "交由 Jev 决策",
286
+ llm: "交由轻量模型分类",
287
+ current: "保持当前路由",
288
+ };
289
+ return MAP[next] || (next ? String(next) : "未知走向");
290
+ }
291
+ function formatRuleAnalysisSummary(analysis) {
292
+ if (!analysis || typeof analysis !== "object") return "";
293
+ const parts = [];
294
+ if (analysis.source) {
295
+ const srcMap = {
296
+ "explicit-label": "显式标签匹配",
297
+ keywords: "关键词匹配",
298
+ none: "无匹配",
299
+ tie: "平局冲突",
300
+ };
301
+ parts.push(`来源: ${srcMap[analysis.source] || analysis.source}`);
302
+ }
303
+ if (analysis.profileId) {
304
+ parts.push(`候选类型: ${analysis.profileId}`);
305
+ }
306
+ if (Array.isArray(analysis.matches) && analysis.matches.length > 0) {
307
+ const details = analysis.matches.map((m) => {
308
+ const kw = (m.matchedKeywords || []).map((k) => `${k.keyword}(${k.in === "label" ? "标题" : "内容"})`).join("、");
309
+ return `${m.id}(${m.score}分${kw ? `: ${kw}` : ""})`;
310
+ }).join(";");
311
+ parts.push(`命中项: ${details}`);
312
+ }
313
+ if (Array.isArray(analysis.tiedIds) && analysis.tiedIds.length > 0) {
314
+ parts.push(`平局: ${analysis.tiedIds.join("、")}`);
315
+ }
316
+ return parts.join(" | ");
317
+ }
151
318
  function validateProfiles(rows) {
152
319
  const seen = new Set();
153
320
  for (const row of rows || []) {
@@ -234,9 +401,10 @@
234
401
 
235
402
  function TaskProfileCard(props) {
236
403
  const {
237
- profileId, template, profile, modelRows, groups, saving, custom,
404
+ profileId, template, profile, modelRows, groups, saving, writable = true, custom,
238
405
  onChangeModel, onSetField, onRemove, onRename,
239
406
  } = props;
407
+ const disabled = saving || !writable;
240
408
  const selected = profile?.provider && profile?.model
241
409
  ? modelRows.find(({ group, model }) => group.id === profile.provider && model.id === profile.model)
242
410
  : null;
@@ -259,27 +427,27 @@
259
427
  react.createElement("h3", { className: "tm-title" }, title),
260
428
  react.createElement("p", { className: "tm-detail" }, detail)),
261
429
  custom ? react.createElement("button", {
262
- className: "tm-button tm-danger", type: "button", disabled: saving, onClick: () => onRemove(profileId),
430
+ className: "tm-button tm-danger", type: "button", disabled, onClick: () => onRemove(profileId),
263
431
  }, "删除") : null),
264
432
  custom ? react.createElement("div", { className: "tm-grid" },
265
433
  react.createElement("label", { className: "tm-field" },
266
434
  react.createElement("span", { className: "tm-label" }, "任务类型 ID"),
267
435
  react.createElement("input", {
268
- className: "tm-input", value: profileId, disabled: saving,
436
+ className: "tm-input", value: profileId, disabled,
269
437
  onChange: (event) => onRename(profileId, event.target.value),
270
438
  "aria-label": `${profileId} 任务类型 ID`,
271
439
  })),
272
440
  react.createElement("label", { className: "tm-field" },
273
441
  react.createElement("span", { className: "tm-label" }, "显示名称"),
274
442
  react.createElement("input", {
275
- className: "tm-input", value: profile?.name ?? "", disabled: saving,
443
+ className: "tm-input", value: profile?.name ?? "", disabled,
276
444
  onChange: (event) => onSetField(profileId, { name: event.target.value }),
277
445
  "aria-label": `${profileId} 显示名称`,
278
446
  }))) : null,
279
447
  custom ? react.createElement("label", { className: "tm-field" },
280
448
  react.createElement("span", { className: "tm-label" }, "任务描述(供 Jev / 轻量模型判断)"),
281
449
  react.createElement("input", {
282
- className: "tm-input", value: profile?.detail ?? "", disabled: saving,
450
+ className: "tm-input", value: profile?.detail ?? "", disabled,
283
451
  onChange: (event) => onSetField(profileId, { detail: event.target.value }),
284
452
  "aria-label": `${profileId} 任务描述`,
285
453
  })) : null,
@@ -287,7 +455,7 @@
287
455
  react.createElement("label", { className: "tm-field" },
288
456
  react.createElement("span", { className: "tm-label" }, "模型"),
289
457
  react.createElement("select", {
290
- className: "tm-select", value: profile?.provider && profile?.model ? modelKey(profile.provider, profile.model) : "", disabled: saving,
458
+ className: "tm-select", value: profile?.provider && profile?.model ? modelKey(profile.provider, profile.model) : "", disabled,
291
459
  onChange: (event) => onChangeModel(profileId, event.target.value), "aria-label": `${profileId} 任务模型`,
292
460
  },
293
461
  react.createElement("option", { value: "" }, "不启用(保持当前路由)"),
@@ -295,7 +463,7 @@
295
463
  react.createElement("label", { className: "tm-field" },
296
464
  react.createElement("span", { className: "tm-label" }, "思考档位"),
297
465
  react.createElement("select", {
298
- className: "tm-select", value: profile?.reasoningEffort || "", disabled: !profile || efforts.length === 0 || saving,
466
+ className: "tm-select", value: profile?.reasoningEffort || "", disabled: !profile || efforts.length === 0 || disabled,
299
467
  onChange: (event) => onSetField(profileId, { reasoningEffort: event.target.value }), "aria-label": `${profileId} 任务思考档位`,
300
468
  },
301
469
  react.createElement("option", { value: "" }, "模型默认"),
@@ -303,13 +471,13 @@
303
471
  react.createElement("label", { className: "tm-field" },
304
472
  react.createElement("span", { className: "tm-label" }, "匹配关键词(逗号分隔;留空则只依赖 Jev/轻量模型)"),
305
473
  react.createElement("input", {
306
- className: "tm-input", value: keywords, disabled: !profile || saving,
474
+ className: "tm-input", value: keywords, disabled: !profile || disabled,
307
475
  onChange: (event) => onSetField(profileId, { keywords: splitKeywords(event.target.value) }),
308
476
  "aria-label": `${profileId} 匹配关键词`,
309
477
  })),
310
478
  react.createElement("label", { className: "tm-check" },
311
479
  react.createElement("input", {
312
- type: "checkbox", checked: profile?.enabled !== false && profile !== undefined, disabled: !profile || saving,
480
+ type: "checkbox", checked: profile?.enabled !== false && profile !== undefined, disabled: !profile || disabled,
313
481
  onChange: (event) => onSetField(profileId, { enabled: event.target.checked }),
314
482
  }),
315
483
  "启用此任务类型"));
@@ -357,6 +525,15 @@
357
525
  return value?.models && typeof value.models === "object" ? value.models : {};
358
526
  }
359
527
 
528
+ async function loadRoutingDetails(childIds) {
529
+ if (!Array.isArray(childIds) || childIds.length === 0) return { models: {}, details: {} };
530
+ const value = await settingsRequest("routingDetails", { childIds });
531
+ return {
532
+ models: value?.models && typeof value.models === "object" ? value.models : {},
533
+ details: value?.details && typeof value.details === "object" ? value.details : {},
534
+ };
535
+ }
536
+
360
537
  function sameNavigable(next, previous) {
361
538
  if (next === previous) return true;
362
539
  if (!Array.isArray(next) || !Array.isArray(previous) || next.length !== previous.length) return false;
@@ -385,7 +562,7 @@
385
562
  * request. The durable node and Definition stay owned by the host.
386
563
  */
387
564
  function WorkflowRunPanel(props) {
388
- const { node, sessionId, openSession, loadModels, useSessions, useSessionStatus } = props;
565
+ const { node, sessionId, openSession, loadModels, loadDetails, useSessions, useSessionStatus } = props;
389
566
  const phases = Array.isArray(node?.data?.phases) ? node.data.phases : [];
390
567
  const allMembers = phases.flatMap((phase) => (Array.isArray(phase.members) ? phase.members : []));
391
568
  const [runOpen, setRunOpen] = react.useState(node?.data?.status !== "completed");
@@ -393,6 +570,7 @@
393
570
  phases.filter((phase) => (phase.members || []).some((member) => member.status !== "completed")).map((phase) => phase.key),
394
571
  ));
395
572
  const [models, setModels] = react.useState({});
573
+ const [details, setDetails] = react.useState({});
396
574
  const statuses = typeof useSessionStatus === "function"
397
575
  ? useSessionStatus((value) => value)
398
576
  : new Map();
@@ -407,14 +585,34 @@
407
585
 
408
586
  react.useEffect(() => {
409
587
  const childIds = childKey === "" ? [] : childKey.split("\u0000");
410
- if (childIds.length === 0 || typeof loadModels !== "function") return () => {};
588
+ if (childIds.length === 0 || (typeof loadDetails !== "function" && typeof loadModels !== "function")) return () => {};
411
589
  let cancelled = false;
590
+ let inFlight = false;
412
591
  const load = async () => {
592
+ if (inFlight) return;
593
+ inFlight = true;
413
594
  try {
414
- const value = await loadModels(childIds);
415
- if (!cancelled) setModels(value || {});
595
+ if (typeof loadDetails === "function") {
596
+ const res = await loadDetails(childIds);
597
+ if (!cancelled && res && typeof res === "object") {
598
+ if (res.models && typeof res.models === "object") setModels((prev) => ({ ...prev, ...res.models }));
599
+ if (res.details && typeof res.details === "object") setDetails((prev) => ({ ...prev, ...res.details }));
600
+ }
601
+ } else if (typeof loadModels === "function") {
602
+ const res = await loadModels(childIds);
603
+ if (!cancelled && res && typeof res === "object") {
604
+ if ("models" in res || "details" in res) {
605
+ if (res.models && typeof res.models === "object") setModels((prev) => ({ ...prev, ...res.models }));
606
+ if (res.details && typeof res.details === "object") setDetails((prev) => ({ ...prev, ...res.details }));
607
+ } else {
608
+ setModels((prev) => ({ ...prev, ...res }));
609
+ }
610
+ }
611
+ }
416
612
  } catch {
417
613
  // The model badge is additive; keep the last known values.
614
+ } finally {
615
+ inFlight = false;
418
616
  }
419
617
  };
420
618
  void load();
@@ -423,7 +621,7 @@
423
621
  cancelled = true;
424
622
  if (timer !== undefined) clearInterval(timer);
425
623
  };
426
- }, [childKey, loadModels, hasRunning]);
624
+ }, [childKey, loadDetails, loadModels, hasRunning]);
427
625
 
428
626
  const toggleRun = () => setRunOpen((value) => !value);
429
627
  const togglePhase = (key) => setOpenPhases((current) => {
@@ -462,10 +660,13 @@
462
660
  phaseOpen ? react.createElement("div", { className: "tm-wf-members" },
463
661
  ...members.map((member, memberIndex) => {
464
662
  const info = models[member.childId];
465
- const model = typeof info?.model === "string" ? info.model : "";
663
+ const detail = details[member.childId];
664
+ const effective = detail?.effectiveRoute || info;
665
+ const model = typeof effective?.model === "string" ? effective.model : "";
466
666
  const pending = model === "" && member.status === "running";
467
667
  const target = navigable.find((entry) => entry.id === member.childId);
468
668
  const clickable = target !== undefined && typeof openSession === "function";
669
+ const tooltip = formatMemberTitle({ info, detail, pending });
469
670
  const content = [
470
671
  react.createElement("span", { className: `tm-wf-dot tm-wf-${member.status || "running"}`, key: "dot" }),
471
672
  react.createElement("span", { className: "tm-wf-label", key: "label", title: member.label || "" }, member.label || "空成员名"),
@@ -473,9 +674,8 @@
473
674
  ? react.createElement("span", {
474
675
  className: `tm-wf-model ${pending ? "tm-wf-model-pending" : ""}`,
475
676
  key: "model",
476
- title: info
477
- ? `${info.provider}/${info.model}${info.reasoningEffort ? ` · ${info.reasoningEffort}` : ""}`
478
- : "等待子代理首次请求",
677
+ title: tooltip,
678
+ "aria-label": tooltip,
479
679
  }, model || "模型…")
480
680
  : null,
481
681
  react.createElement("span", { className: "tm-wf-member-status", key: "status" }, statusText(member.status || "running")),
@@ -502,6 +702,7 @@
502
702
  const [error, setError] = react.useState("");
503
703
  const [writable, setWritable] = react.useState(false);
504
704
  const [revision, setRevision] = react.useState(0);
705
+ const [conflict, setConflict] = react.useState(false);
505
706
  const [groups, setGroups] = react.useState([]);
506
707
  const [failures, setFailures] = react.useState([]);
507
708
  const [routing, setRouting] = react.useState({ ...DEFAULT_ROUTING, classifier: { ...DEFAULT_ROUTING.classifier } });
@@ -513,7 +714,68 @@
513
714
  const [jevTest, setJevTest] = react.useState("");
514
715
  const [saved, setSaved] = react.useState("");
515
716
  const [saving, setSaving] = react.useState(false);
717
+
718
+ const [previewLabel, setPreviewLabel] = react.useState("");
719
+ const [previewText, setPreviewText] = react.useState("");
720
+ const [previewRunning, setPreviewRunning] = react.useState(false);
721
+ const [previewResult, setPreviewResult] = react.useState(null);
722
+ const [previewError, setPreviewError] = react.useState("");
723
+
516
724
  const dirtyRef = react.useRef(false);
725
+ const revisionRef = react.useRef(0);
726
+ const writableRef = react.useRef(false);
727
+ const previewReqIdRef = react.useRef(0);
728
+ const jevTestReqIdRef = react.useRef(0);
729
+ revisionRef.current = revision;
730
+ writableRef.current = writable;
731
+
732
+ const clearPreview = () => {
733
+ previewReqIdRef.current++;
734
+ setPreviewResult(null);
735
+ setPreviewError("");
736
+ setPreviewRunning(false);
737
+ };
738
+
739
+ const clearJevTest = () => {
740
+ jevTestReqIdRef.current++;
741
+ setJevTest("");
742
+ setJevTesting(false);
743
+ };
744
+
745
+ const updateRouting = (updater) => {
746
+ clearPreview();
747
+ dirtyRef.current = true;
748
+ setRouting(updater);
749
+ };
750
+
751
+ const updateProfiles = (updater) => {
752
+ clearPreview();
753
+ dirtyRef.current = true;
754
+ setProfiles(updater);
755
+ };
756
+
757
+ const updateJev = (updater) => {
758
+ clearPreview();
759
+ clearJevTest();
760
+ dirtyRef.current = true;
761
+ setJev(updater);
762
+ };
763
+
764
+ const updateJevKey = (value) => {
765
+ clearJevTest();
766
+ dirtyRef.current = true;
767
+ setJevKey(value);
768
+ };
769
+
770
+ const changePreviewLabel = (value) => {
771
+ clearPreview();
772
+ setPreviewLabel(value);
773
+ };
774
+
775
+ const changePreviewText = (value) => {
776
+ clearPreview();
777
+ setPreviewText(value);
778
+ };
517
779
 
518
780
  const load = async (silent = false) => {
519
781
  if (!silent) {
@@ -539,26 +801,47 @@
539
801
  const nextRouting = {
540
802
  ...DEFAULT_ROUTING,
541
803
  ...(settingsView.value?.routing || {}),
804
+ decisionTimeoutMs: Number.isFinite(Number(settingsView.value?.routing?.decisionTimeoutMs))
805
+ ? clampNumber(Math.round(Number(settingsView.value?.routing?.decisionTimeoutMs)), DEFAULT_ROUTING.decisionTimeoutMs, 100, 60000)
806
+ : DEFAULT_ROUTING.decisionTimeoutMs,
542
807
  classifier: { ...DEFAULT_ROUTING.classifier, ...(settingsView.value?.routing?.classifier || {}) },
543
808
  };
544
809
  const nextProfiles = normalizeProfiles(settingsView.value?.profiles);
545
810
  const nextJev = normalizeJev(settingsView.value?.jev);
546
- setWritable(settingsView.writable);
547
- setRevision(settingsView.revision);
548
811
  setGroups(modelsValue.groups || []);
549
812
  setFailures(modelsValue.failures || []);
550
- if (!silent || !dirtyRef.current) {
813
+
814
+ const reconciliation = reconcileSettings({
815
+ currentRevision: revisionRef.current,
816
+ currentWritable: writableRef.current,
817
+ remoteRevision: settingsView.revision,
818
+ remoteWritable: settingsView.writable,
819
+ silent,
820
+ dirty: dirtyRef.current,
821
+ });
822
+
823
+ setConflict(reconciliation.conflict);
824
+ setWritable(reconciliation.writable);
825
+ setRevision(reconciliation.revision);
826
+
827
+ if (reconciliation.shouldApplyRemote) {
828
+ clearPreview();
829
+ clearJevTest();
551
830
  setRouting(nextRouting);
552
831
  setProfiles(nextProfiles);
553
832
  setJev(nextJev);
554
833
  setSaved(JSON.stringify({ routing: nextRouting, profiles: nextProfiles, jev: nextJev }));
555
834
  }
556
835
  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: "" }));
836
+ .then((value) => {
837
+ if (value && typeof value?.configured === "boolean") {
838
+ setJevStatus({
839
+ configured: value.configured,
840
+ model: typeof value?.model === "string" ? value.model : "",
841
+ });
842
+ }
843
+ })
844
+ .catch(() => {});
562
845
  setStatus("ready");
563
846
  } catch (cause) {
564
847
  if (!silent) {
@@ -589,7 +872,7 @@
589
872
  const statusText = statusMessage(status, writable, dirty);
590
873
 
591
874
  const setProfile = (id, patch) => {
592
- setProfiles((current) => {
875
+ updateProfiles((current) => {
593
876
  const target = normalizeId(id);
594
877
  const index = current.findIndex((entry) => normalizeId(entry.id) === target);
595
878
  if (patch === null) return current.filter((entry) => normalizeId(entry.id) !== target);
@@ -615,12 +898,12 @@
615
898
  let suffix = 1;
616
899
  while (byProfile.has(`custom-${suffix}`)) suffix++;
617
900
  const id = `custom-${suffix}`;
618
- setProfiles((current) => [...current, normalizeProfile({ id, name: `自定义任务 ${suffix}`, provider: "", model: "" })]);
901
+ updateProfiles((current) => [...current, normalizeProfile({ id, name: `自定义任务 ${suffix}`, provider: "", model: "" })]);
619
902
  };
620
903
 
621
904
  const renameProfile = (oldId, nextValue) => {
622
905
  const target = normalizeId(oldId);
623
- setProfiles((current) => current.map((entry) => normalizeId(entry.id) === target
906
+ updateProfiles((current) => current.map((entry) => normalizeId(entry.id) === target
624
907
  ? normalizeProfile({ ...entry, id: normalizeId(nextValue) })
625
908
  : entry));
626
909
  };
@@ -630,11 +913,11 @@
630
913
  const changeClassifier = (value) => {
631
914
  const selected = parseModelKey(value);
632
915
  if (!selected) {
633
- setRouting((current) => ({ ...current, classifier: { provider: "", model: "", reasoningEffort: "" } }));
916
+ updateRouting((current) => ({ ...current, classifier: { provider: "", model: "", reasoningEffort: "" } }));
634
917
  return;
635
918
  }
636
919
  const row = modelRows.find(({ group, model }) => group.id === selected.provider && model.id === selected.model);
637
- setRouting((current) => ({
920
+ updateRouting((current) => ({
638
921
  ...current,
639
922
  classifier: {
640
923
  provider: selected.provider,
@@ -647,24 +930,60 @@
647
930
  const refreshJevStatus = async () => {
648
931
  try {
649
932
  const value = await settingsRequest("jevStatus", {}, props.api);
650
- setJevStatus({ configured: value?.configured === true, model: typeof value?.model === "string" ? value.model : "" });
933
+ if (value && typeof value?.configured === "boolean") {
934
+ setJevStatus({ configured: value.configured, model: typeof value?.model === "string" ? value.model : "" });
935
+ }
651
936
  } catch {
652
- setJevStatus({ configured: false, model: "" });
937
+ // 网络 status 失败不能误改已保存状态
653
938
  }
654
939
  };
655
940
 
656
941
  const testJev = async () => {
942
+ const reqId = ++jevTestReqIdRef.current;
657
943
  setJevTesting(true);
658
944
  setJevTest("");
659
945
  try {
660
946
  const payload = { ...(jevKey.trim() === "" ? {} : { apiKey: jevKey.trim() }), jev: normalizeJev(jev) };
661
947
  const result = await settingsRequest("testJev", payload, props.api);
662
- setJevTest(`✓ Jev 连接成功${result?.latencyMs === undefined ? "" : `(${result.latencyMs}ms)`}`);
663
- setJevStatus((current) => ({ ...current, configured: true }));
948
+ if (jevTestReqIdRef.current === reqId) {
949
+ setJevTest(`✓ Jev 连接测试成功${result?.latencyMs === undefined ? "" : `(${result.latencyMs}ms)`}`);
950
+ }
664
951
  } catch (cause) {
665
- setJevTest(`Jev 测试失败:${cause instanceof Error ? cause.message : String(cause)}`);
952
+ if (jevTestReqIdRef.current === reqId) {
953
+ setJevTest(`Jev 测试失败:${cause instanceof Error ? cause.message : String(cause)}`);
954
+ }
666
955
  } finally {
667
- setJevTesting(false);
956
+ if (jevTestReqIdRef.current === reqId) {
957
+ setJevTesting(false);
958
+ }
959
+ }
960
+ };
961
+
962
+ const runPreview = async () => {
963
+ if (validation || previewRunning || (!previewLabel.trim() && !previewText.trim())) return;
964
+ const reqId = ++previewReqIdRef.current;
965
+ setPreviewRunning(true);
966
+ setPreviewError("");
967
+ setPreviewResult(null);
968
+ try {
969
+ const section = buildDraftSection({ routing, profiles, jev });
970
+ const res = await settingsRequest("previewRules", {
971
+ label: previewLabel.slice(0, 300),
972
+ text: previewText.slice(0, 6000),
973
+ section,
974
+ }, props.api);
975
+ if (previewReqIdRef.current === reqId) {
976
+ setPreviewResult(res);
977
+ }
978
+ } catch {
979
+ // error不暴露原文任务或Key
980
+ if (previewReqIdRef.current === reqId) {
981
+ setPreviewError("规则试运行请求失败");
982
+ }
983
+ } finally {
984
+ if (previewReqIdRef.current === reqId) {
985
+ setPreviewRunning(false);
986
+ }
668
987
  }
669
988
  };
670
989
 
@@ -677,32 +996,8 @@
677
996
  await settingsRequest("saveJevKey", { apiKey: jevKey.trim() }, props.api);
678
997
  setJevKey("");
679
998
  }
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
999
  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
- },
1000
+ section: buildDraftSection({ routing, profiles, jev }),
706
1001
  expectedRevision: revision,
707
1002
  }, props.api);
708
1003
  await load(false);
@@ -726,39 +1021,56 @@
726
1021
  react.createElement("h2", null, "任务模型"),
727
1022
  react.createElement("p", { className: "tm-intro" }, INTRO_TEXT),
728
1023
  feedback ? react.createElement("div", { className: "tm-feedback tm-error", role: "alert" }, feedback) : null,
1024
+ conflict ? react.createElement("div", { className: "tm-feedback tm-warn", role: "alert" },
1025
+ react.createElement("span", null, "外部配置已更新,当前草稿存在冲突。"),
1026
+ react.createElement("button", {
1027
+ className: "tm-button", type: "button", style: { height: 26, padding: "0 10px", fontSize: 12 },
1028
+ onClick: () => load(false),
1029
+ }, "重新加载最新配置")) : null,
729
1030
  react.createElement("section", { className: "tm-card" },
730
1031
  react.createElement("div", null,
731
1032
  react.createElement("h3", { className: "tm-title" }, "子任务路由"),
732
1033
  react.createElement("p", { className: "tm-detail" }, "对所有会话委派的子任务生效;不要求任何 Agent Preset,也不会改写主会话模型。")),
733
1034
  react.createElement("label", { className: "tm-check" },
734
1035
  react.createElement("input", {
735
- type: "checkbox", checked: routing.enabled, disabled: saving,
736
- onChange: (event) => setRouting((current) => ({ ...current, enabled: event.target.checked })),
1036
+ type: "checkbox", checked: routing.enabled, disabled: saving || !writable,
1037
+ onChange: (event) => updateRouting((current) => ({ ...current, enabled: event.target.checked })),
737
1038
  }),
738
1039
  "启用按任务类型路由子任务"),
739
1040
  react.createElement("div", { className: "tm-grid" },
740
1041
  react.createElement("label", { className: "tm-field" },
741
1042
  react.createElement("span", { className: "tm-label" }, "规则未命中时"),
742
1043
  react.createElement("select", {
743
- className: "tm-select", value: routing.fallback, disabled: saving,
744
- onChange: (event) => setRouting((current) => ({ ...current, fallback: event.target.value })),
1044
+ className: "tm-select", value: routing.fallback, disabled: saving || !writable,
1045
+ onChange: (event) => updateRouting((current) => ({ ...current, fallback: event.target.value })),
745
1046
  },
746
1047
  react.createElement("option", { value: "taskRole" }, "保持当前路由"),
747
1048
  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
- "尊重子任务显式选择的模型"))),
1049
+ react.createElement("label", { className: "tm-field" },
1050
+ react.createElement("span", { className: "tm-label" }, "决策总超时(毫秒,100-60000)"),
1051
+ react.createElement("input", {
1052
+ className: "tm-input", type: "number", min: 100, max: 60000, step: 100,
1053
+ value: routing.decisionTimeoutMs ?? DEFAULT_ROUTING.decisionTimeoutMs, disabled: saving || !writable,
1054
+ onChange: (event) => updateRouting((current) => ({
1055
+ ...current,
1056
+ decisionTimeoutMs: clampNumber(Math.round(Number(event.target.value) || DEFAULT_ROUTING.decisionTimeoutMs), DEFAULT_ROUTING.decisionTimeoutMs, 100, 60000),
1057
+ })),
1058
+ "aria-label": "决策总超时",
1059
+ }))),
1060
+ react.createElement("div", { className: "tm-field" },
1061
+ react.createElement("span", { className: "tm-label" }, "决策选项"),
1062
+ react.createElement("label", { className: "tm-check" },
1063
+ react.createElement("input", {
1064
+ type: "checkbox", checked: routing.useJev, disabled: saving || !writable,
1065
+ onChange: (event) => updateRouting((current) => ({ ...current, useJev: event.target.checked })),
1066
+ }),
1067
+ "规则未命中时优先问 Jev"),
1068
+ react.createElement("label", { className: "tm-check" },
1069
+ react.createElement("input", {
1070
+ type: "checkbox", checked: routing.respectExplicitChildModel, disabled: saving || !writable,
1071
+ onChange: (event) => updateRouting((current) => ({ ...current, respectExplicitChildModel: event.target.checked })),
1072
+ }),
1073
+ "尊重子任务显式选择的模型")),
762
1074
  react.createElement("p", { className: "tm-note" }, "任务类型按子代理首次请求决定一次,后续请求沿用同一模型。规则并列时不会按配置顺序随机选择。")),
763
1075
  react.createElement("div", { className: "tm-card" },
764
1076
  react.createElement("div", null,
@@ -768,7 +1080,7 @@
768
1080
  react.createElement("label", { className: "tm-field" },
769
1081
  react.createElement("span", { className: "tm-label" }, "分类模型"),
770
1082
  react.createElement("select", {
771
- className: "tm-select", value: classifier.provider && classifier.model ? modelKey(classifier.provider, classifier.model) : "", disabled: saving,
1083
+ className: "tm-select", value: classifier.provider && classifier.model ? modelKey(classifier.provider, classifier.model) : "", disabled: saving || !writable,
772
1084
  onChange: (event) => changeClassifier(event.target.value),
773
1085
  },
774
1086
  react.createElement("option", { value: "" }, "不配置"),
@@ -777,8 +1089,8 @@
777
1089
  react.createElement("label", { className: "tm-field" },
778
1090
  react.createElement("span", { className: "tm-label" }, "分类思考档位"),
779
1091
  react.createElement("select", {
780
- className: "tm-select", value: classifier.reasoningEffort || "", disabled: classifierEfforts.length === 0 || saving,
781
- onChange: (event) => setRouting((current) => ({
1092
+ className: "tm-select", value: classifier.reasoningEffort || "", disabled: classifierEfforts.length === 0 || saving || !writable,
1093
+ onChange: (event) => updateRouting((current) => ({
782
1094
  ...current,
783
1095
  classifier: { ...current.classifier, reasoningEffort: event.target.value },
784
1096
  })),
@@ -794,6 +1106,7 @@
794
1106
  modelRows,
795
1107
  groups,
796
1108
  saving,
1109
+ writable,
797
1110
  custom: false,
798
1111
  onChangeModel: changeProfileModel,
799
1112
  onSetField: setProfileField,
@@ -808,6 +1121,7 @@
808
1121
  modelRows,
809
1122
  groups,
810
1123
  saving,
1124
+ writable,
811
1125
  custom: true,
812
1126
  onChangeModel: changeProfileModel,
813
1127
  onSetField: setProfileField,
@@ -816,59 +1130,106 @@
816
1130
  }))),
817
1131
  react.createElement("div", { className: "tm-actions" },
818
1132
  react.createElement("button", { className: "tm-button", type: "button", disabled: saving || !writable, onClick: addProfile }, "添加任务类型")),
1133
+ react.createElement("section", { className: "tm-card" },
1134
+ react.createElement("div", null,
1135
+ react.createElement("h3", { className: "tm-title" }, "规则试运行"),
1136
+ react.createElement("p", { className: "tm-detail" }, "输入子任务标题与描述,测试规则匹配与路由决策走向(含未保存草稿);不调用任何模型或写配置。")),
1137
+ react.createElement("label", { className: "tm-field" },
1138
+ react.createElement("span", { className: "tm-label" }, "子任务 Label / 标题(最多 300 字符)"),
1139
+ react.createElement("input", {
1140
+ className: "tm-input",
1141
+ maxLength: 300,
1142
+ value: previewLabel,
1143
+ disabled: previewRunning || saving,
1144
+ placeholder: "例如:实现用户登录校验",
1145
+ onChange: (event) => changePreviewLabel(event.target.value),
1146
+ "aria-label": "试运行任务 Label",
1147
+ })),
1148
+ react.createElement("label", { className: "tm-field" },
1149
+ react.createElement("span", { className: "tm-label" }, "子任务内容 / 描述(最多 6000 字符)"),
1150
+ react.createElement("textarea", {
1151
+ className: "tm-textarea",
1152
+ maxLength: 6000,
1153
+ value: previewText,
1154
+ disabled: previewRunning || saving,
1155
+ placeholder: "例如:任务详细描述或提示词内容…",
1156
+ onChange: (event) => changePreviewText(event.target.value),
1157
+ "aria-label": "试运行任务内容",
1158
+ })),
1159
+ react.createElement("div", { className: "tm-actions tm-actions-left" },
1160
+ react.createElement("button", {
1161
+ className: "tm-button tm-primary",
1162
+ type: "button",
1163
+ disabled: Boolean(validation) || previewRunning || saving || (!previewLabel.trim() && !previewText.trim()),
1164
+ onClick: runPreview,
1165
+ }, previewRunning ? "试运行中…" : "试运行规则")),
1166
+ previewError ? react.createElement("p", { className: "tm-feedback tm-error", role: "alert" }, previewError) : null,
1167
+ previewResult ? react.createElement("div", { className: "tm-preview-result" },
1168
+ react.createElement("div", null,
1169
+ react.createElement("strong", null, "走向:"),
1170
+ formatNextStep(previewResult.next),
1171
+ " · ",
1172
+ react.createElement("span", null, formatRoutingDetail(previewResult.reason))),
1173
+ previewResult.profile ? react.createElement("div", null,
1174
+ react.createElement("strong", null, "目标模型:"),
1175
+ `${previewResult.profile.name || previewResult.profile.id} (${previewResult.profile.provider}/${previewResult.profile.model}${previewResult.profile.reasoningEffort ? ` · ${previewResult.profile.reasoningEffort}` : ""})`) : null,
1176
+ formatRuleAnalysisSummary(previewResult.ruleAnalysis) ? react.createElement("div", null,
1177
+ react.createElement("strong", null, "规则分析:"),
1178
+ formatRuleAnalysisSummary(previewResult.ruleAnalysis)) : null
1179
+ ) : null),
819
1180
  react.createElement("section", { className: "tm-card" },
820
1181
  react.createElement("div", { className: "tm-card-head" },
821
1182
  react.createElement("div", null,
822
1183
  react.createElement("h3", { className: "tm-title" }, "Jev 决策"),
823
1184
  react.createElement("p", { className: "tm-detail" }, "可选。规则未命中时由 TypeSafe Jev 在已配置任务类型中选择。")),
824
1185
  jevStatus.configured
825
- ? react.createElement("span", { className: "tm-badge tm-badge-ok" }, "已连接")
1186
+ ? react.createElement("span", { className: "tm-badge tm-badge-ok" }, "已配置")
826
1187
  : react.createElement("span", { className: "tm-badge tm-badge-warn" }, "未配置")),
827
1188
  react.createElement("label", { className: "tm-check" },
828
1189
  react.createElement("input", {
829
- type: "checkbox", checked: jev.enabled, disabled: saving,
830
- onChange: (event) => setJev((current) => ({ ...current, enabled: event.target.checked })),
1190
+ type: "checkbox", checked: jev.enabled, disabled: saving || !writable,
1191
+ onChange: (event) => updateJev((current) => ({ ...current, enabled: event.target.checked })),
831
1192
  }),
832
1193
  "启用 Jev 决策"),
833
1194
  react.createElement("div", { className: "tm-grid" },
834
1195
  react.createElement("label", { className: "tm-field" },
835
1196
  react.createElement("span", { className: "tm-label" }, "API Key"),
836
1197
  react.createElement("input", {
837
- className: "tm-input", type: "password", value: jevKey, disabled: saving,
1198
+ className: "tm-input", type: "password", value: jevKey, disabled: saving || !writable,
838
1199
  placeholder: jevStatus.configured ? "已保存,留空不修改" : "ts-...",
839
- onChange: (event) => setJevKey(event.target.value),
1200
+ onChange: (event) => updateJevKey(event.target.value),
840
1201
  })),
841
1202
  react.createElement("label", { className: "tm-field" },
842
1203
  react.createElement("span", { className: "tm-label" }, "Base URL"),
843
1204
  react.createElement("input", {
844
- className: "tm-input", value: jev.baseURL, disabled: saving,
845
- onChange: (event) => setJev((current) => ({ ...current, baseURL: event.target.value })),
1205
+ className: "tm-input", value: jev.baseURL, disabled: saving || !writable,
1206
+ onChange: (event) => updateJev((current) => ({ ...current, baseURL: event.target.value })),
846
1207
  }))),
847
1208
  react.createElement("div", { className: "tm-grid tm-grid-3" },
848
1209
  react.createElement("label", { className: "tm-field" },
849
1210
  react.createElement("span", { className: "tm-label" }, "模型"),
850
1211
  react.createElement("input", {
851
- className: "tm-input", value: jev.model, disabled: saving,
852
- onChange: (event) => setJev((current) => ({ ...current, model: event.target.value })),
1212
+ className: "tm-input", value: jev.model, disabled: saving || !writable,
1213
+ onChange: (event) => updateJev((current) => ({ ...current, model: event.target.value })),
853
1214
  })),
854
1215
  react.createElement("label", { className: "tm-field" },
855
1216
  react.createElement("span", { className: "tm-label" }, "最低置信度"),
856
1217
  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) })),
1218
+ className: "tm-input", type: "number", min: 0, max: 1, step: 0.05, value: jev.minConfidence, disabled: saving || !writable,
1219
+ onChange: (event) => updateJev((current) => ({ ...current, minConfidence: clampNumber(event.target.value, DEFAULT_JEV.minConfidence, 0, 1) })),
859
1220
  })),
860
1221
  react.createElement("label", { className: "tm-field" },
861
1222
  react.createElement("span", { className: "tm-label" }, "超时(毫秒)"),
862
1223
  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) })),
1224
+ className: "tm-input", type: "number", min: 100, max: 60000, step: 100, value: jev.timeoutMs, disabled: saving || !writable,
1225
+ onChange: (event) => updateJev((current) => ({ ...current, timeoutMs: clampNumber(event.target.value, DEFAULT_JEV.timeoutMs, 100, 60000) })),
865
1226
  }))),
866
1227
  react.createElement("div", { className: "tm-grid" },
867
1228
  react.createElement("label", { className: "tm-field" },
868
1229
  react.createElement("span", { className: "tm-label" }, "发送上下文字符上限"),
869
1230
  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) })),
1231
+ className: "tm-input", type: "number", min: 500, max: 100000, step: 500, value: jev.maxStateChars, disabled: saving || !writable,
1232
+ onChange: (event) => updateJev((current) => ({ ...current, maxStateChars: clampNumber(event.target.value, DEFAULT_JEV.maxStateChars, 500, 100000) })),
872
1233
  })),
873
1234
  react.createElement("div", { className: "tm-field tm-field-actions" },
874
1235
  react.createElement("span", { className: "tm-label" }, "连接"),
@@ -911,6 +1272,7 @@
911
1272
  priority: -1,
912
1273
  inject: () => ({
913
1274
  loadModels: loadChildModels,
1275
+ loadDetails: loadRoutingDetails,
914
1276
  openSession: (target) => {
915
1277
  if (typeof ctx.uiWorkspace?.openSession === "function") ctx.uiWorkspace.openSession(target);
916
1278
  },
@@ -926,6 +1288,8 @@
926
1288
  splitKeywords, normalizeKeywords, rowsFromGroups, taskTemplateOf, profileDefaults, normalizeProfile,
927
1289
  normalizeProfiles, profilesById, validateRouting, validateProfiles, normalizeJev, validateJev, clampNumber,
928
1290
  statusMessage, createSettingsRequest, TaskProfileCard,
1291
+ buildDraftSection, reconcileSettings, formatRoutingDetail, formatMemberTitle,
1292
+ formatNextStep, formatRuleAnalysisSummary,
929
1293
  };
930
1294
  return module.exports;
931
1295
  },