@ran-sh/dsh-crew 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.claude-plugin/plugin.json +8 -8
  2. package/.mcp.json +8 -8
  3. package/LICENSE +21 -21
  4. package/README.de.md +359 -359
  5. package/README.es.md +359 -359
  6. package/README.fr.md +359 -359
  7. package/README.hi.md +359 -359
  8. package/README.id.md +359 -359
  9. package/README.ja.md +359 -359
  10. package/README.ko.md +359 -359
  11. package/README.md +94 -180
  12. package/README.pt.md +359 -359
  13. package/README.ru.md +359 -359
  14. package/README.th.md +359 -359
  15. package/README.tr.md +359 -359
  16. package/README.vi.md +359 -359
  17. package/README.zh-TW.md +359 -359
  18. package/README.zh.md +94 -180
  19. package/agents/ds-flash.md +26 -26
  20. package/agents/ds-pro.md +32 -32
  21. package/agents/ds-reviewer.md +23 -23
  22. package/agents/ds-worker.md +22 -22
  23. package/codex/agents/ds-flash.toml +30 -30
  24. package/codex/agents/ds-pro.toml +31 -31
  25. package/codex/agents/ds-reviewer.toml +28 -28
  26. package/codex/agents/ds-worker.toml +28 -28
  27. package/codex/prompts/dsh-config.md +3 -3
  28. package/codex/prompts/dsh-status.md +1 -1
  29. package/commands/config.md +11 -11
  30. package/commands/off.md +5 -5
  31. package/commands/on.md +5 -5
  32. package/commands/status.md +5 -5
  33. package/cordis.patch.yml +4 -4
  34. package/lib/client.js +1655 -974
  35. package/official-web-bridge/cordis.patch.yml +4 -0
  36. package/official-web-bridge/entry.mjs +1 -0
  37. package/official-web-bridge/lib/client.js +3446 -0
  38. package/official-web-bridge/package.json +26 -0
  39. package/package.json +133 -131
  40. package/scripts/build-client.mjs +32 -28
  41. package/scripts/live-crew-smoke.mjs +39 -39
  42. package/scripts/live-policy-matrix.mjs +177 -177
  43. package/scripts/policy-probe.mjs +101 -101
  44. package/scripts/setup.mjs +284 -284
  45. package/scripts/smoke-real.mjs +110 -110
  46. package/scripts/smoke.mjs +78 -78
  47. package/scripts/verify-installer-fix.mjs +26 -26
  48. package/scripts/verify-official-bridge-e2e.mjs +159 -0
  49. package/src/adaptive-routing.mjs +260 -260
  50. package/src/client/activation-summary.tsx +64 -64
  51. package/src/client/collapsible-sections.mjs +55 -0
  52. package/src/client/entry.tsx +236 -236
  53. package/src/client/index.tsx +1213 -1120
  54. package/src/config-readiness.mjs +59 -59
  55. package/src/delivery.mjs +205 -205
  56. package/src/dsh-cli-runtime.mjs +458 -447
  57. package/src/failure-classification.mjs +172 -172
  58. package/src/hub/entry.mjs +98 -98
  59. package/src/hub-client.mjs +132 -132
  60. package/src/hub-compatibility.mjs +49 -49
  61. package/src/i18n.mjs +19 -19
  62. package/src/install/cli.mjs +28 -28
  63. package/src/install/install-legacy.mjs +462 -462
  64. package/src/install/install.mjs +451 -451
  65. package/src/install/npx-lifecycle.mjs +1119 -1055
  66. package/src/install/official-web.mjs +132 -0
  67. package/src/mcp-runtime.mjs +257 -257
  68. package/src/model-catalog.mjs +173 -173
  69. package/src/model-routing.mjs +391 -391
  70. package/src/official-web-bridge.mjs +196 -0
  71. package/src/policy.mjs +197 -197
  72. package/src/readiness-matrix.mjs +169 -169
  73. package/src/runtime-controls.mjs +90 -90
  74. package/src/runtime-identity.mjs +108 -108
  75. package/src/server.mjs +477 -477
  76. package/src/status-shard.mjs +52 -52
  77. package/src/structured-error-code.mjs +38 -38
  78. package/src/vision-route.mjs +138 -138
  79. package/src/workflow-runtime.mjs +573 -573
  80. package/src/workflow.mjs +160 -160
  81. package/src/workspace-audit.mjs +231 -231
  82. package/src/workspace-isolation.mjs +365 -365
  83. package/statusline/statusline.sh +14 -14
  84. package/statusline/worker-segment.sh +35 -35
  85. package/worker.cordis.yml +77 -77
package/lib/client.js CHANGED
@@ -1,12 +1,78 @@
1
1
  window.__ModuleLoader__.load({ id: "@ran-sh/dsh-crew", factory: (require) => {
2
2
  var module = { exports: {} }; var exports = module.exports;
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ //#region \0rolldown/runtime.js
5
+ var __create = Object.create;
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
13
+ key = keys[i];
14
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
22
+ value: mod,
23
+ enumerable: true
24
+ }) : target, mod));
25
+ //#endregion
4
26
  let react = require("react");
27
+ react = __toESM(react, 1);
5
28
  let react_dom = require("react-dom");
6
29
  let react_jsx_runtime = require("react/jsx-runtime");
30
+ //#region src/client/collapsible-sections.mjs
31
+ const SETTINGS_SECTION_STORAGE_KEY = "dsh-crew.settings-sections.v1";
32
+ const SETTINGS_SECTION_IDS = Object.freeze([
33
+ "integrations",
34
+ "workflow",
35
+ "flash",
36
+ "pro",
37
+ "dispatch",
38
+ "runtime",
39
+ "multimodal",
40
+ "providers",
41
+ "jobs"
42
+ ]);
43
+ function createDefaultSectionState() {
44
+ return Object.fromEntries(SETTINGS_SECTION_IDS.map((id) => [id, id === "workflow"]));
45
+ }
46
+ function readSectionState(storage, key = SETTINGS_SECTION_STORAGE_KEY) {
47
+ const defaults = createDefaultSectionState();
48
+ if (!storage?.getItem) return defaults;
49
+ try {
50
+ const parsed = JSON.parse(storage.getItem(key) ?? "null");
51
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return defaults;
52
+ for (const id of SETTINGS_SECTION_IDS) if (typeof parsed[id] === "boolean") defaults[id] = parsed[id];
53
+ } catch {
54
+ return defaults;
55
+ }
56
+ return defaults;
57
+ }
58
+ function writeSectionState(storage, state, key = SETTINGS_SECTION_STORAGE_KEY) {
59
+ if (!storage?.setItem) return;
60
+ try {
61
+ storage.setItem(key, JSON.stringify(Object.fromEntries(SETTINGS_SECTION_IDS.map((id) => [id, state[id] === true]))));
62
+ } catch {}
63
+ }
64
+ function setEverySection(expanded) {
65
+ return Object.fromEntries(SETTINGS_SECTION_IDS.map((id) => [id, expanded === true]));
66
+ }
67
+ function openSections(state, ids) {
68
+ const next = { ...state };
69
+ for (const id of ids) if (SETTINGS_SECTION_IDS.includes(id)) next[id] = true;
70
+ return next;
71
+ }
72
+ //#endregion
7
73
  //#region src/client/index.tsx
8
- const inject = ["slots", "locale"];
9
- const API = "/_dsh/dsh-crew";
74
+ const inject$1 = ["slots", "locale"];
75
+ const API$1 = "/_dsh/dsh-crew";
10
76
  const COPY = {
11
77
  zh: {
12
78
  label: "DSH Crew",
@@ -21,6 +87,23 @@ const COPY = {
21
87
  restore: "还原",
22
88
  confirmRestore: (name) => `确定从 ${name} 移除 dsh-crew 集成?(settings 会先备份)`,
23
89
  globalConfig: "全局配置",
90
+ expandAll: "全部展开",
91
+ collapseAll: "全部折叠",
92
+ modelCount: (count) => `${count} 个模型`,
93
+ providerCount: (count) => `${count} 个 Provider`,
94
+ jobCount: (count) => `${count} 个任务`,
95
+ runningCount: (count) => `${count} 个运行中`,
96
+ sectionNames: {
97
+ integrations: "宿主集成",
98
+ workflow: "工作流",
99
+ flash: "Flash",
100
+ pro: "Pro",
101
+ dispatch: "派发策略",
102
+ runtime: "运行连接",
103
+ multimodal: "视觉与生图",
104
+ providers: "自定义 Provider",
105
+ jobs: "Worker 任务"
106
+ },
24
107
  globalHint: "修改即时保存到 ~/.config/dsh-crew/config.json;CC / Codex 的新会话自动读取为默认值(会话内可用 /dsh-crew:config 临时覆盖)。",
25
108
  orchestration: "Agent 编排",
26
109
  enableSubagents: "启用子 Agent",
@@ -238,6 +321,23 @@ const COPY = {
238
321
  restore: "Restore",
239
322
  confirmRestore: (name) => `Remove the dsh-crew integration from ${name}? (settings are backed up first)`,
240
323
  globalConfig: "Global configuration",
324
+ expandAll: "Expand all",
325
+ collapseAll: "Collapse all",
326
+ modelCount: (count) => `${count} models`,
327
+ providerCount: (count) => `${count} providers`,
328
+ jobCount: (count) => `${count} jobs`,
329
+ runningCount: (count) => `${count} running`,
330
+ sectionNames: {
331
+ integrations: "Host integrations",
332
+ workflow: "Workflow",
333
+ flash: "Flash",
334
+ pro: "Pro",
335
+ dispatch: "Dispatch policy",
336
+ runtime: "Runtime connection",
337
+ multimodal: "Vision & image generation",
338
+ providers: "Custom providers",
339
+ jobs: "Worker jobs"
340
+ },
241
341
  globalHint: "Changes save instantly to ~/.config/dsh-crew/config.json; new CC / Codex sessions pick them up as defaults (override per session with /dsh-crew:config).",
242
342
  orchestration: "Agent orchestration",
243
343
  enableSubagents: "Enable Subagents",
@@ -506,6 +606,31 @@ const S = {
506
606
  cursor: "pointer",
507
607
  fontSize: 12.5
508
608
  },
609
+ sectionShell: {
610
+ border: "1px solid rgba(128,128,128,0.24)",
611
+ borderRadius: 11,
612
+ overflow: "hidden",
613
+ background: "rgba(128,128,128,0.025)"
614
+ },
615
+ sectionButton: {
616
+ width: "100%",
617
+ padding: "10px 13px",
618
+ border: 0,
619
+ background: "transparent",
620
+ color: "inherit",
621
+ cursor: "pointer",
622
+ display: "flex",
623
+ alignItems: "center",
624
+ gap: 9,
625
+ textAlign: "left",
626
+ font: "inherit"
627
+ },
628
+ sectionBody: {
629
+ padding: "2px 11px 12px",
630
+ display: "flex",
631
+ flexDirection: "column",
632
+ gap: 8
633
+ },
509
634
  chip: (on) => ({
510
635
  fontSize: 11.5,
511
636
  padding: "1px 8px",
@@ -672,6 +797,69 @@ function CustomSelect({ value, options, onChange, minWidth }) {
672
797
  }, o.value))
673
798
  }), document.body)] });
674
799
  }
800
+ function sectionSummary(...parts) {
801
+ return parts.filter((part) => part !== false && part !== null && part !== void 0 && part !== "").join(" · ");
802
+ }
803
+ function sectionStorage() {
804
+ if (typeof window === "undefined") return null;
805
+ try {
806
+ return window.localStorage;
807
+ } catch {
808
+ return null;
809
+ }
810
+ }
811
+ function CollapsibleSection({ sectionId, title, summary, expanded, onToggle, children }) {
812
+ const bodyId = `dsh-crew-section-${sectionId}`;
813
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
814
+ style: S.sectionShell,
815
+ "data-section-id": sectionId,
816
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
817
+ type: "button",
818
+ style: S.sectionButton,
819
+ "aria-expanded": expanded,
820
+ "aria-controls": bodyId,
821
+ onClick: onToggle,
822
+ children: [
823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
824
+ "aria-hidden": "true",
825
+ style: {
826
+ width: 14,
827
+ opacity: .62,
828
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
829
+ transition: "transform 140ms ease"
830
+ },
831
+ children: "›"
832
+ }),
833
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
834
+ style: {
835
+ fontWeight: 650,
836
+ fontSize: 13,
837
+ minWidth: 92
838
+ },
839
+ children: title
840
+ }),
841
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
842
+ title: summary,
843
+ style: {
844
+ flex: 1,
845
+ minWidth: 0,
846
+ fontSize: 11.5,
847
+ opacity: .58,
848
+ overflow: "hidden",
849
+ textOverflow: "ellipsis",
850
+ whiteSpace: "nowrap"
851
+ },
852
+ children: summary
853
+ })
854
+ ]
855
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
856
+ id: bodyId,
857
+ hidden: !expanded,
858
+ style: expanded ? S.sectionBody : void 0,
859
+ children
860
+ })]
861
+ });
862
+ }
675
863
  function sourceLabel(source) {
676
864
  if (source === "claude-code") return "CC";
677
865
  if (source === "codex") return "Codex";
@@ -704,6 +892,30 @@ function WorkersPanel({ ctx }) {
704
892
  const [modelPickerTier, setModelPickerTier] = (0, react.useState)(null);
705
893
  const [modelQuery, setModelQuery] = (0, react.useState)("");
706
894
  const [testResult, setTestResult] = (0, react.useState)(null);
895
+ const [expandedSections, setExpandedSections] = (0, react.useState)(() => readSectionState(sectionStorage()));
896
+ const toggleSection = (sectionId) => {
897
+ setExpandedSections((current) => ({
898
+ ...current,
899
+ [sectionId]: !current[sectionId]
900
+ }));
901
+ };
902
+ const toggleAllSections = (expanded) => setExpandedSections(setEverySection(expanded));
903
+ (0, react.useEffect)(() => {
904
+ writeSectionState(sectionStorage(), expandedSections);
905
+ }, [expandedSections]);
906
+ (0, react.useEffect)(() => {
907
+ if (modelCatalogError) setExpandedSections((current) => openSections(current, [
908
+ "workflow",
909
+ "flash",
910
+ "pro"
911
+ ]));
912
+ }, [modelCatalogError]);
913
+ (0, react.useEffect)(() => {
914
+ if (testResult && !testResult.busy && testResult.ok === false) setExpandedSections((current) => openSections(current, ["providers"]));
915
+ }, [testResult]);
916
+ (0, react.useEffect)(() => {
917
+ if (jobs.some((job) => job.status === "running")) setExpandedSections((current) => openSections(current, ["jobs"]));
918
+ }, [jobs]);
707
919
  /**
708
920
  * The panel is served from disk on every load, but the hub's routes are
709
921
  * registered at instance boot — after an upgrade the new UI can call a route
@@ -721,7 +933,7 @@ function WorkersPanel({ ctx }) {
721
933
  }, [copy]);
722
934
  /** Every request carries the active locale: the panel is the authority on it
723
935
  * (DSH's setting may be unset), and the hub localizes its own strings from it. */
724
- const withLang = (0, react.useCallback)((path) => `${API}${path}${path.includes("?") ? "&" : "?"}lang=${locale === "zh" ? "zh" : "en"}`, [locale]);
936
+ const withLang = (0, react.useCallback)((path) => `${API$1}${path}${path.includes("?") ? "&" : "?"}lang=${locale === "zh" ? "zh" : "en"}`, [locale]);
725
937
  const get = (0, react.useCallback)(async (path) => readJson(await fetch(withLang(path), { cache: "no-store" }), path), [readJson, withLang]);
726
938
  const post = (0, react.useCallback)(async (path, body) => readJson(await fetch(withLang(path), {
727
939
  method: "POST",
@@ -1130,24 +1342,59 @@ function WorkersPanel({ ctx }) {
1130
1342
  style: { opacity: .75 },
1131
1343
  children: copy.intro
1132
1344
  }),
1133
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1134
- style: S.section,
1135
- children: copy.integrations
1136
- }),
1137
1345
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1138
1346
  style: {
1139
1347
  display: "flex",
1140
- flexDirection: "column",
1141
- gap: 8
1348
+ alignItems: "center",
1349
+ gap: 7,
1350
+ marginTop: 5
1142
1351
  },
1143
- children: [integrationRow("Claude Code", !!status?.claude?.installed, status?.claude?.installed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1144
- style: S.chip(!!status?.claude?.hud),
1145
- children: copy.hud
1146
- }) : null, "claude", "claude-uninstall", copy.tips.claude), integrationRow("Codex", !!status?.codex?.installed, null, "codex", "codex-uninstall", copy.tips.codex)]
1352
+ children: [
1353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1354
+ style: {
1355
+ ...S.section,
1356
+ margin: 0,
1357
+ flex: 1
1358
+ },
1359
+ children: copy.globalConfig
1360
+ }),
1361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1362
+ type: "button",
1363
+ style: {
1364
+ ...S.btn,
1365
+ padding: "3px 9px"
1366
+ },
1367
+ onClick: () => toggleAllSections(true),
1368
+ children: copy.expandAll
1369
+ }),
1370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1371
+ type: "button",
1372
+ style: {
1373
+ ...S.btn,
1374
+ padding: "3px 9px"
1375
+ },
1376
+ onClick: () => toggleAllSections(false),
1377
+ children: copy.collapseAll
1378
+ })
1379
+ ]
1147
1380
  }),
1148
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1149
- style: S.section,
1150
- children: copy.globalConfig
1381
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
1382
+ sectionId: "integrations",
1383
+ title: copy.sectionNames.integrations,
1384
+ summary: sectionSummary(status?.claude?.installed ? "Claude ✓" : "Claude ○", status?.codex?.installed ? "Codex ✓" : "Codex ○"),
1385
+ expanded: !!expandedSections.integrations,
1386
+ onToggle: () => toggleSection("integrations"),
1387
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1388
+ style: {
1389
+ display: "flex",
1390
+ flexDirection: "column",
1391
+ gap: 8
1392
+ },
1393
+ children: [integrationRow("Claude Code", !!status?.claude?.installed, status?.claude?.installed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1394
+ style: S.chip(!!status?.claude?.hud),
1395
+ children: copy.hud
1396
+ }) : null, "claude", "claude-uninstall", copy.tips.claude), integrationRow("Codex", !!status?.codex?.installed, null, "codex", "codex-uninstall", copy.tips.codex)]
1397
+ })
1151
1398
  }),
1152
1399
  config && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1153
1400
  (() => {
@@ -1573,798 +1820,887 @@ function WorkersPanel({ ctx }) {
1573
1820
  ]
1574
1821
  });
1575
1822
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1576
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1577
- style: S.group,
1578
- children: copy.groupWorkflow
1823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
1824
+ sectionId: "workflow",
1825
+ title: copy.sectionNames.workflow,
1826
+ summary: sectionSummary(config.worker_state ?? "auto", config.review_state ?? "auto", mode, config.isolation ?? "worktree", `×${config.max_parallel ?? 3}`),
1827
+ expanded: !!expandedSections.workflow,
1828
+ onToggle: () => toggleSection("workflow"),
1829
+ children: [
1830
+ block({
1831
+ t: copy.roleWorker,
1832
+ d: copy.roleStateHint
1833
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1834
+ style: S.field,
1835
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1836
+ style: S.fieldLabel,
1837
+ children: copy.workerStateLabel
1838
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1839
+ value: config.worker_state ?? "auto",
1840
+ onChange: (v) => field("worker_state", v),
1841
+ options: [
1842
+ "auto",
1843
+ "manual",
1844
+ "disabled"
1845
+ ].map((s) => ({
1846
+ value: s,
1847
+ label: copy.tierStateDesc[s]
1848
+ }))
1849
+ })]
1850
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1851
+ style: S.field,
1852
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1853
+ style: S.fieldLabel,
1854
+ children: copy.reviewStateLabel
1855
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1856
+ value: config.review_state ?? "auto",
1857
+ onChange: (v) => field("review_state", v),
1858
+ options: [
1859
+ "auto",
1860
+ "manual",
1861
+ "disabled"
1862
+ ].map((s) => ({
1863
+ value: s,
1864
+ label: copy.tierStateDesc[s]
1865
+ }))
1866
+ })]
1867
+ })] })),
1868
+ block({
1869
+ t: copy.autoReview,
1870
+ d: copy.autoReviewHint
1871
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1872
+ style: {
1873
+ ...S.field,
1874
+ flexDirection: "row",
1875
+ alignItems: "center",
1876
+ gap: 6
1877
+ },
1878
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1879
+ type: "checkbox",
1880
+ checked: config.auto_review ?? !!config.pro_reviews_flash,
1881
+ onChange: (e) => field("auto_review", e.target.checked)
1882
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1883
+ style: { fontSize: 12.5 },
1884
+ children: copy.autoReview
1885
+ })]
1886
+ }), false),
1887
+ block({
1888
+ t: copy.isolation,
1889
+ d: copy.isolationHint
1890
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1891
+ style: S.field,
1892
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1893
+ style: S.fieldLabel,
1894
+ children: copy.isolation
1895
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1896
+ value: config.isolation ?? "worktree",
1897
+ onChange: (v) => field("isolation", v),
1898
+ options: ["worktree", "shared"].map((s) => ({
1899
+ value: s,
1900
+ label: copy.isolationDesc[s]
1901
+ }))
1902
+ })]
1903
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1904
+ style: S.field,
1905
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1906
+ style: S.fieldLabel,
1907
+ children: copy.maxParallel
1908
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1909
+ type: "number",
1910
+ min: 1,
1911
+ max: 16,
1912
+ value: config.max_parallel ?? 3,
1913
+ onChange: (e) => fieldLocal("max_parallel", clampNum(e.target.value)),
1914
+ onBlur: () => field("max_parallel", clampNum(config.max_parallel)),
1915
+ style: S.input
1916
+ })]
1917
+ })] })),
1918
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1919
+ style: {
1920
+ fontSize: 11,
1921
+ opacity: .6,
1922
+ marginTop: 3
1923
+ },
1924
+ children: copy.legacyCompatibility
1925
+ }),
1926
+ block({
1927
+ t: copy.orchestration,
1928
+ d: copy.enableSubagentsHint
1929
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1930
+ style: {
1931
+ ...S.field,
1932
+ flexDirection: "row",
1933
+ alignItems: "center",
1934
+ gap: 6
1935
+ },
1936
+ title: copy.enableSubagentsHint,
1937
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1938
+ type: "checkbox",
1939
+ checked: !!config.subagents_enabled,
1940
+ onChange: (e) => field("subagents_enabled", e.target.checked)
1941
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1942
+ style: { fontSize: 12.5 },
1943
+ children: config.subagents_enabled === false ? copy.subagentsKept : copy.enableSubagents
1944
+ })]
1945
+ }), false),
1946
+ block({
1947
+ t: copy.mainAgentMode,
1948
+ d: copy.mainAgentModeHint
1949
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1951
+ style: S.field,
1952
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1953
+ style: S.fieldLabel,
1954
+ children: copy.mainAgentMode
1955
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1956
+ value: config.main_agent_mode ?? "direct-allowed",
1957
+ onChange: (v) => field("main_agent_mode", v),
1958
+ options: [
1959
+ "direct-allowed",
1960
+ "coordinator-first",
1961
+ "dispatcher-only"
1962
+ ].map((m) => ({
1963
+ value: m,
1964
+ label: copy.mainAgentModeDesc[m]
1965
+ }))
1966
+ })]
1967
+ }),
1968
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1969
+ style: S.field,
1970
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1971
+ style: S.fieldLabel,
1972
+ children: copy.collaborationMode
1973
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1974
+ value: mode,
1975
+ onChange: (v) => field("collaboration_mode", v),
1976
+ options: [
1977
+ "flash-only",
1978
+ "pro-only",
1979
+ "balanced",
1980
+ "review-pipeline",
1981
+ "custom"
1982
+ ].map((m) => ({
1983
+ value: m,
1984
+ label: copy.collaborationModeDesc[m]
1985
+ }))
1986
+ })]
1987
+ }),
1988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1989
+ style: S.field,
1990
+ title: copy.workerProviderHint,
1991
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1992
+ style: S.fieldLabel,
1993
+ children: copy.workerProvider
1994
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1995
+ value: config.worker_provider_mode ?? "follow-dsh",
1996
+ onChange: (v) => field("worker_provider_mode", v),
1997
+ options: ["follow-dsh", "deepseek-official"].map((m) => ({
1998
+ value: m,
1999
+ label: copy.workerProviderDesc[m]
2000
+ }))
2001
+ })]
2002
+ })
2003
+ ] })),
2004
+ block({
2005
+ t: copy.workerModels,
2006
+ d: modelCatalog ? copy.modelPoolSummary(modelCatalog.provider_count ?? 0, modelCatalog.model_count ?? 0) : copy.catalogFailed
2007
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2009
+ type: "button",
2010
+ style: S.btn,
2011
+ disabled: modelCatalogBusy,
2012
+ onClick: () => {
2013
+ refreshHarnessModels();
2014
+ },
2015
+ children: modelCatalogBusy ? copy.refreshingModels : copy.refreshHarnessModels
2016
+ }),
2017
+ modelCatalog?.partial && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2018
+ style: {
2019
+ fontSize: 11.5,
2020
+ color: "#c98735"
2021
+ },
2022
+ children: copy.partialCatalog
2023
+ }),
2024
+ modelCatalogError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2025
+ style: {
2026
+ fontSize: 11.5,
2027
+ color: "#c55"
2028
+ },
2029
+ children: modelCatalogError
2030
+ })
2031
+ ] }), false),
2032
+ block({
2033
+ t: copy.routing,
2034
+ d: ""
2035
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2036
+ style: {
2037
+ ...S.field,
2038
+ flexDirection: "row",
2039
+ alignItems: "center",
2040
+ gap: 6,
2041
+ paddingBottom: 5
2042
+ },
2043
+ title: copy.escalateHint,
2044
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2045
+ type: "checkbox",
2046
+ checked: !!config.escalate_on_failure,
2047
+ onChange: (e) => field("escalate_on_failure", e.target.checked)
2048
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2049
+ style: { fontSize: 12 },
2050
+ children: copy.escalate
2051
+ })]
2052
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2053
+ style: {
2054
+ ...S.field,
2055
+ flexDirection: "row",
2056
+ alignItems: "center",
2057
+ gap: 6,
2058
+ paddingBottom: 5
2059
+ },
2060
+ title: mode === "review-pipeline" ? copy.proReviewsLocked : copy.proReviewsFlashHint,
2061
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2062
+ type: "checkbox",
2063
+ disabled: mode === "review-pipeline",
2064
+ checked: mode === "review-pipeline" ? true : !!config.pro_reviews_flash,
2065
+ onChange: (e) => field("pro_reviews_flash", e.target.checked)
2066
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2067
+ style: { fontSize: 12 },
2068
+ children: [copy.proReviewsFlash, mode === "review-pipeline" ? `(${copy.proReviewsLocked})` : ""]
2069
+ })]
2070
+ })] }))
2071
+ ]
2072
+ }),
2073
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2074
+ sectionId: "flash",
2075
+ title: copy.sectionNames.flash,
2076
+ summary: sectionSummary(effState("flash"), copy.modelCount(priorityFor("flash").length), priorityFor("flash")[0]?.model ?? copy.harnessDefault),
2077
+ expanded: !!expandedSections.flash,
2078
+ onToggle: () => toggleSection("flash"),
2079
+ children: tierCard("flash", {
2080
+ t: copy.tierCardFlash,
2081
+ d: "implementation · direct validation · Delivery Report"
2082
+ })
2083
+ }),
2084
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2085
+ sectionId: "pro",
2086
+ title: copy.sectionNames.pro,
2087
+ summary: sectionSummary(effState("pro"), copy.modelCount(priorityFor("pro").length), priorityFor("pro")[0]?.model ?? copy.harnessDefault),
2088
+ expanded: !!expandedSections.pro,
2089
+ onToggle: () => toggleSection("pro"),
2090
+ children: tierCard("pro", {
2091
+ t: copy.tierCardPro,
2092
+ d: "manual advanced work · review pipeline"
2093
+ })
2094
+ })
2095
+ ] });
2096
+ })(),
2097
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2098
+ sectionId: "dispatch",
2099
+ title: copy.sectionNames.dispatch,
2100
+ summary: sectionSummary(config.default_tier, config.default_effort, config.tier_policy, config.escalate_on_failure && copy.escalate),
2101
+ expanded: !!expandedSections.dispatch,
2102
+ onToggle: () => toggleSection("dispatch"),
2103
+ children: block(copy.cardDispatch, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2104
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2105
+ style: S.field,
2106
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2107
+ style: S.fieldLabel,
2108
+ children: copy.tier
2109
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2110
+ value: config.default_tier,
2111
+ onChange: (v) => field("default_tier", v),
2112
+ options: [{ value: "flash" }, { value: "pro" }]
2113
+ })]
1579
2114
  }),
1580
- block({
1581
- t: copy.roleWorker,
1582
- d: copy.roleStateHint
1583
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2115
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1584
2116
  style: S.field,
1585
2117
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1586
2118
  style: S.fieldLabel,
1587
- children: copy.workerStateLabel
2119
+ children: copy.effort
1588
2120
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1589
- value: config.worker_state ?? "auto",
1590
- onChange: (v) => field("worker_state", v),
2121
+ value: config.default_effort,
2122
+ onChange: (v) => field("default_effort", v),
1591
2123
  options: [
1592
- "auto",
1593
- "manual",
1594
- "disabled"
1595
- ].map((s) => ({
1596
- value: s,
1597
- label: copy.tierStateDesc[s]
1598
- }))
2124
+ { value: "off" },
2125
+ { value: "high" },
2126
+ { value: "max" }
2127
+ ]
1599
2128
  })]
1600
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2129
+ }),
2130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1601
2131
  style: S.field,
1602
2132
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1603
2133
  style: S.fieldLabel,
1604
- children: copy.reviewStateLabel
2134
+ children: copy.tierPolicy
1605
2135
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1606
- value: config.review_state ?? "auto",
1607
- onChange: (v) => field("review_state", v),
2136
+ value: config.tier_policy,
2137
+ onChange: (v) => field("tier_policy", v),
1608
2138
  options: [
1609
2139
  "auto",
1610
- "manual",
1611
- "disabled"
1612
- ].map((s) => ({
1613
- value: s,
1614
- label: copy.tierStateDesc[s]
2140
+ "flash-only",
2141
+ "pro-only"
2142
+ ].map((p) => ({
2143
+ value: p,
2144
+ label: copy.tierPolicyDesc[p]
1615
2145
  }))
1616
2146
  })]
1617
- })] })),
1618
- block({
1619
- t: copy.autoReview,
1620
- d: copy.autoReviewHint
1621
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2147
+ }),
2148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1622
2149
  style: {
1623
2150
  ...S.field,
1624
2151
  flexDirection: "row",
1625
2152
  alignItems: "center",
1626
- gap: 6
2153
+ gap: 6,
2154
+ paddingBottom: 5
1627
2155
  },
2156
+ title: copy.escalateHint,
1628
2157
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1629
2158
  type: "checkbox",
1630
- checked: config.auto_review ?? !!config.pro_reviews_flash,
1631
- onChange: (e) => field("auto_review", e.target.checked)
2159
+ checked: !!config.escalate_on_failure,
2160
+ onChange: (e) => field("escalate_on_failure", e.target.checked)
1632
2161
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1633
- style: { fontSize: 12.5 },
1634
- children: copy.autoReview
2162
+ style: { fontSize: 12 },
2163
+ children: copy.escalate
2164
+ })]
2165
+ }),
2166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2167
+ style: S.field,
2168
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2169
+ style: S.fieldLabel,
2170
+ children: copy.presetFlash
2171
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2172
+ value: config.preset_flash ?? "default",
2173
+ onChange: (v) => field("preset_flash", v),
2174
+ options: presetOptions
1635
2175
  })]
1636
- }) }), false),
1637
- block({
1638
- t: copy.isolation,
1639
- d: copy.isolationHint
1640
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2176
+ }),
2177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2178
+ style: S.field,
2179
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2180
+ style: S.fieldLabel,
2181
+ children: copy.presetPro
2182
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2183
+ value: config.preset_pro ?? "default",
2184
+ onChange: (v) => field("preset_pro", v),
2185
+ options: presetOptions
2186
+ })]
2187
+ })
2188
+ ] }))
2189
+ }),
2190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2191
+ sectionId: "runtime",
2192
+ title: copy.sectionNames.runtime,
2193
+ summary: sectionSummary(config.mode, `${config.default_timeout_seconds}s`, config.hub_url),
2194
+ expanded: !!expandedSections.runtime,
2195
+ onToggle: () => toggleSection("runtime"),
2196
+ children: block(copy.cardRuntime, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1641
2198
  style: S.field,
1642
2199
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1643
2200
  style: S.fieldLabel,
1644
- children: copy.isolation
2201
+ children: copy.mode
1645
2202
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1646
- value: config.isolation ?? "worktree",
1647
- onChange: (v) => field("isolation", v),
1648
- options: ["worktree", "shared"].map((s) => ({
1649
- value: s,
1650
- label: copy.isolationDesc[s]
2203
+ value: config.mode,
2204
+ onChange: (v) => field("mode", v),
2205
+ options: [
2206
+ "auto",
2207
+ "hub",
2208
+ "standalone"
2209
+ ].map((m) => ({
2210
+ value: m,
2211
+ label: copy.modeDesc[m]
1651
2212
  }))
1652
2213
  })]
1653
- }) })),
1654
- block({
1655
- t: copy.maxParallel,
1656
- d: copy.maxParallelHint
1657
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2214
+ }),
2215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1658
2216
  style: S.field,
1659
2217
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1660
2218
  style: S.fieldLabel,
1661
- children: copy.maxParallel
2219
+ children: copy.timeout
1662
2220
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2221
+ style: {
2222
+ ...S.input,
2223
+ width: "100%",
2224
+ boxSizing: "border-box"
2225
+ },
1663
2226
  type: "number",
1664
- min: 1,
1665
- max: 16,
1666
- value: config.max_parallel ?? 3,
1667
- onChange: (e) => fieldLocal("max_parallel", clampNum(e.target.value)),
1668
- onBlur: () => field("max_parallel", clampNum(config.max_parallel)),
1669
- style: S.input
2227
+ min: 60,
2228
+ max: 7200,
2229
+ value: config.default_timeout_seconds,
2230
+ onChange: (e) => fieldLocal("default_timeout_seconds", Number(e.target.value)),
2231
+ onBlur: () => applyPatch({ default_timeout_seconds: config.default_timeout_seconds })
1670
2232
  })]
1671
- }) })),
1672
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1673
- style: {
1674
- fontSize: 11,
1675
- opacity: .6,
1676
- marginTop: 8,
1677
- marginBottom: 2
1678
- },
1679
- children: copy.legacyCompatibility
1680
- }),
1681
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1682
- style: S.group,
1683
- children: copy.orchestration
1684
2233
  }),
1685
- block({
1686
- t: copy.orchestration,
1687
- d: copy.enableSubagentsHint
1688
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1689
- style: {
1690
- ...S.field,
1691
- flexDirection: "row",
1692
- alignItems: "center",
1693
- gap: 6
1694
- },
1695
- title: copy.enableSubagentsHint,
1696
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1697
- type: "checkbox",
1698
- checked: !!config.subagents_enabled,
1699
- onChange: (e) => field("subagents_enabled", e.target.checked)
1700
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1701
- style: { fontSize: 12.5 },
1702
- children: config.subagents_enabled === false ? copy.subagentsKept : copy.enableSubagents
1703
- })]
1704
- }) }), false),
1705
- block({
1706
- t: copy.mainAgentMode,
1707
- d: copy.mainAgentModeHint
1708
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1709
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1710
- style: S.field,
1711
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1712
- style: S.fieldLabel,
1713
- children: copy.mainAgentMode
1714
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1715
- value: config.main_agent_mode ?? "direct-allowed",
1716
- onChange: (v) => field("main_agent_mode", v),
1717
- options: [
1718
- "direct-allowed",
1719
- "coordinator-first",
1720
- "dispatcher-only"
1721
- ].map((m) => ({
1722
- value: m,
1723
- label: copy.mainAgentModeDesc[m]
1724
- }))
1725
- })]
1726
- }),
1727
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1728
- style: S.field,
1729
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1730
- style: S.fieldLabel,
1731
- children: copy.collaborationMode
1732
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1733
- value: mode,
1734
- onChange: (v) => field("collaboration_mode", v),
1735
- options: [
1736
- "flash-only",
1737
- "pro-only",
1738
- "balanced",
1739
- "review-pipeline",
1740
- "custom"
1741
- ].map((m) => ({
1742
- value: m,
1743
- label: copy.collaborationModeDesc[m]
1744
- }))
1745
- })]
1746
- }),
1747
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1748
- style: S.field,
1749
- title: copy.workerProviderHint,
1750
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1751
- style: S.fieldLabel,
1752
- children: copy.workerProvider
1753
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1754
- value: config.worker_provider_mode ?? "follow-dsh",
1755
- onChange: (v) => field("worker_provider_mode", v),
1756
- options: ["follow-dsh", "deepseek-official"].map((m) => ({
1757
- value: m,
1758
- label: copy.workerProviderDesc[m]
1759
- }))
1760
- })]
1761
- })
1762
- ] })),
1763
- block({
1764
- t: copy.workerModels,
1765
- d: modelCatalog ? copy.modelPoolSummary(modelCatalog.provider_count ?? 0, modelCatalog.model_count ?? 0) : copy.catalogFailed
1766
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1767
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1768
- type: "button",
1769
- style: S.btn,
1770
- disabled: modelCatalogBusy,
1771
- onClick: () => {
1772
- refreshHarnessModels();
1773
- },
1774
- children: modelCatalogBusy ? copy.refreshingModels : copy.refreshHarnessModels
1775
- }),
1776
- modelCatalog?.partial && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1777
- style: {
1778
- fontSize: 11.5,
1779
- color: "#c98735"
1780
- },
1781
- children: copy.partialCatalog
1782
- }),
1783
- modelCatalogError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2234
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2235
+ style: S.field,
2236
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2237
+ style: S.fieldLabel,
2238
+ children: copy.hubUrl
2239
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1784
2240
  style: {
1785
- fontSize: 11.5,
1786
- color: "#c55"
2241
+ ...S.input,
2242
+ width: "100%",
2243
+ boxSizing: "border-box"
1787
2244
  },
1788
- children: modelCatalogError
1789
- })
1790
- ] }), false),
1791
- tierCard("flash", {
1792
- t: copy.tierCardFlash,
1793
- d: "implementation · direct validation · Delivery Report"
1794
- }),
1795
- tierCard("pro", {
1796
- t: copy.tierCardPro,
1797
- d: "manual advanced work · review pipeline"
1798
- }),
1799
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1800
- style: S.group,
1801
- children: copy.routing
1802
- }),
1803
- block({
1804
- t: copy.routing,
1805
- d: ""
1806
- }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1807
- style: {
1808
- ...S.field,
1809
- flexDirection: "row",
1810
- alignItems: "center",
1811
- gap: 6,
1812
- paddingBottom: 5
1813
- },
1814
- title: copy.escalateHint,
1815
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1816
- type: "checkbox",
1817
- checked: !!config.escalate_on_failure,
1818
- onChange: (e) => field("escalate_on_failure", e.target.checked)
1819
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1820
- style: { fontSize: 12 },
1821
- children: copy.escalate
2245
+ value: config.hub_url,
2246
+ onChange: (e) => fieldLocal("hub_url", e.target.value),
2247
+ onBlur: () => applyPatch({ hub_url: config.hub_url }),
2248
+ onKeyDown: (e) => {
2249
+ if (e.key === "Enter") e.currentTarget.blur();
2250
+ }
1822
2251
  })]
1823
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2252
+ })
2253
+ ] }))
2254
+ }),
2255
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2256
+ sectionId: "multimodal",
2257
+ title: copy.sectionNames.multimodal,
2258
+ summary: sectionSummary(config.vision_enabled ? `${copy.capVision}: ${config.vision_provider}` : `${copy.capVision}: off`, config.imagegen_enabled ? `${copy.capImagegen}: ${config.imagegen_provider}` : `${copy.capImagegen}: off`),
2259
+ expanded: !!expandedSections.multimodal,
2260
+ onToggle: () => toggleSection("multimodal"),
2261
+ children: block(copy.cardMM, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1824
2263
  style: {
1825
2264
  ...S.field,
1826
2265
  flexDirection: "row",
1827
2266
  alignItems: "center",
1828
2267
  gap: 6,
1829
- paddingBottom: 5
2268
+ gridColumn: "1 / -1"
1830
2269
  },
1831
- title: mode === "review-pipeline" ? copy.proReviewsLocked : copy.proReviewsFlashHint,
1832
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1833
- type: "checkbox",
1834
- disabled: mode === "review-pipeline",
1835
- checked: mode === "review-pipeline" ? true : !!config.pro_reviews_flash,
1836
- onChange: (e) => field("pro_reviews_flash", e.target.checked)
1837
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1838
- style: { fontSize: 12 },
1839
- children: [copy.proReviewsFlash, mode === "review-pipeline" ? `(${copy.proReviewsLocked})` : ""]
1840
- })]
1841
- })] }))
1842
- ] });
1843
- })(),
1844
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1845
- style: S.group,
1846
- children: copy.groupDispatch
1847
- }),
1848
- block(copy.cardDispatch, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1849
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1850
- style: S.field,
1851
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1852
- style: S.fieldLabel,
1853
- children: copy.tier
1854
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1855
- value: config.default_tier,
1856
- onChange: (v) => field("default_tier", v),
1857
- options: [{ value: "flash" }, { value: "pro" }]
1858
- })]
1859
- }),
1860
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1861
- style: S.field,
1862
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1863
- style: S.fieldLabel,
1864
- children: copy.effort
1865
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1866
- value: config.default_effort,
1867
- onChange: (v) => field("default_effort", v),
1868
- options: [
1869
- { value: "off" },
1870
- { value: "high" },
1871
- { value: "max" }
2270
+ title: copy.capRestartHint,
2271
+ children: [
2272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2273
+ type: "checkbox",
2274
+ checked: !!config.vision_enabled,
2275
+ onChange: (e) => field("vision_enabled", e.target.checked)
2276
+ }),
2277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2278
+ style: { fontSize: 12.5 },
2279
+ children: copy.enableCrewVision
2280
+ }),
2281
+ config.vision_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2282
+ style: {
2283
+ fontSize: 11,
2284
+ opacity: .55
2285
+ },
2286
+ children: copy.capDisabledNote
2287
+ })
1872
2288
  ]
1873
- })]
1874
- }),
1875
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1876
- style: S.field,
1877
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1878
- style: S.fieldLabel,
1879
- children: copy.tierPolicy
1880
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1881
- value: config.tier_policy,
1882
- onChange: (v) => field("tier_policy", v),
1883
- options: [
1884
- "auto",
1885
- "flash-only",
1886
- "pro-only"
1887
- ].map((p) => ({
1888
- value: p,
1889
- label: copy.tierPolicyDesc[p]
1890
- }))
1891
- })]
1892
- }),
1893
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1894
- style: {
1895
- ...S.field,
1896
- flexDirection: "row",
1897
- alignItems: "center",
1898
- gap: 6,
1899
- paddingBottom: 5
1900
- },
1901
- title: copy.escalateHint,
1902
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1903
- type: "checkbox",
1904
- checked: !!config.escalate_on_failure,
1905
- onChange: (e) => field("escalate_on_failure", e.target.checked)
1906
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1907
- style: { fontSize: 12 },
1908
- children: copy.escalate
1909
- })]
1910
- }),
1911
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1912
- style: S.field,
1913
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1914
- style: S.fieldLabel,
1915
- children: copy.presetFlash
1916
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1917
- value: config.preset_flash ?? "default",
1918
- onChange: (v) => field("preset_flash", v),
1919
- options: presetOptions
1920
- })]
1921
- }),
1922
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1923
- style: S.field,
1924
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1925
- style: S.fieldLabel,
1926
- children: copy.presetPro
1927
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1928
- value: config.preset_pro ?? "default",
1929
- onChange: (v) => field("preset_pro", v),
1930
- options: presetOptions
1931
- })]
1932
- })
1933
- ] })),
1934
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1935
- style: S.group,
1936
- children: copy.groupRuntime
1937
- }),
1938
- block(copy.cardRuntime, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1939
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1940
- style: S.field,
1941
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1942
- style: S.fieldLabel,
1943
- children: copy.mode
1944
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
1945
- value: config.mode,
1946
- onChange: (v) => field("mode", v),
1947
- options: [
1948
- "auto",
1949
- "hub",
1950
- "standalone"
1951
- ].map((m) => ({
1952
- value: m,
1953
- label: copy.modeDesc[m]
1954
- }))
1955
- })]
1956
- }),
1957
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1958
- style: S.field,
1959
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1960
- style: S.fieldLabel,
1961
- children: copy.timeout
1962
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1963
- style: {
1964
- ...S.input,
1965
- width: "100%",
1966
- boxSizing: "border-box"
1967
- },
1968
- type: "number",
1969
- min: 60,
1970
- max: 7200,
1971
- value: config.default_timeout_seconds,
1972
- onChange: (e) => fieldLocal("default_timeout_seconds", Number(e.target.value)),
1973
- onBlur: () => applyPatch({ default_timeout_seconds: config.default_timeout_seconds })
1974
- })]
1975
- }),
1976
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1977
- style: S.field,
1978
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1979
- style: S.fieldLabel,
1980
- children: copy.hubUrl
1981
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2289
+ }),
2290
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2291
+ style: S.field,
2292
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2293
+ style: S.fieldLabel,
2294
+ children: copy.visionProvider
2295
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2296
+ value: config.vision_provider,
2297
+ onChange: (p) => {
2298
+ const custom = customProviders.find((c) => c.id === p);
2299
+ const m = custom ? custom.models?.[0] ?? "default" : (VISION_MODELS[p] ?? ["default"])[0];
2300
+ setModelAdd(null);
2301
+ setConfig((c) => ({
2302
+ ...c,
2303
+ vision_provider: p,
2304
+ vision_model: m
2305
+ }));
2306
+ applyPatch({
2307
+ vision_provider: p,
2308
+ vision_model: m
2309
+ });
2310
+ if ((p === "grok" || p === "agy") && !dynModels[p]) get(`/vision-models?provider=${p}`).then((r) => {
2311
+ if (r.ok) setDynModels((d) => ({
2312
+ ...d,
2313
+ [p]: r.models
2314
+ }));
2315
+ }).catch(() => {});
2316
+ },
2317
+ options: visionProviderOptions
2318
+ })]
2319
+ }),
2320
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2321
+ style: S.field,
2322
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2323
+ style: S.fieldLabel,
2324
+ children: copy.visionModel
2325
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2326
+ style: {
2327
+ display: "flex",
2328
+ gap: 6,
2329
+ alignItems: "center"
2330
+ },
2331
+ children: [
2332
+ modelAdd === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2333
+ value: config.vision_model,
2334
+ onChange: (v) => field("vision_model", v),
2335
+ options: visionModelOptions
2336
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2337
+ autoFocus: true,
2338
+ style: {
2339
+ ...S.input,
2340
+ width: "100%",
2341
+ boxSizing: "border-box",
2342
+ ...S.mono
2343
+ },
2344
+ value: modelAdd,
2345
+ placeholder: copy.modelPlaceholder,
2346
+ onChange: (e) => setModelAdd(e.target.value),
2347
+ onBlur: commitModelAdd,
2348
+ onKeyDown: (e) => {
2349
+ if (e.key === "Enter") e.currentTarget.blur();
2350
+ if (e.key === "Escape") setModelAdd(null);
2351
+ }
2352
+ }),
2353
+ modelAdd === null && (config.vision_provider === "grok" || config.vision_provider === "agy") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2354
+ type: "button",
2355
+ title: copy.refreshModels,
2356
+ disabled: busy,
2357
+ style: {
2358
+ ...S.btn,
2359
+ padding: "3px 8px",
2360
+ flexShrink: 0
2361
+ },
2362
+ onClick: () => {
2363
+ const p = config.vision_provider;
2364
+ setBusy(true);
2365
+ get(`/vision-models?provider=${p}&refresh=1`).then((r) => {
2366
+ if (r.ok) setDynModels((d) => ({
2367
+ ...d,
2368
+ [p]: r.models
2369
+ }));
2370
+ }).catch(() => {}).finally(() => setBusy(false));
2371
+ },
2372
+ children: "↻"
2373
+ }),
2374
+ modelAdd === null && config.vision_provider !== "off" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2375
+ type: "button",
2376
+ title: copy.addModelTip,
2377
+ style: {
2378
+ ...S.btn,
2379
+ padding: "3px 8px",
2380
+ flexShrink: 0
2381
+ },
2382
+ onClick: () => setModelAdd(""),
2383
+ children: "+"
2384
+ }),
2385
+ modelAdd === null && (extraModels[config.vision_provider] ?? []).includes(config.vision_model) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2386
+ type: "button",
2387
+ title: copy.removeModelTip,
2388
+ style: {
2389
+ ...S.btn,
2390
+ padding: "3px 8px",
2391
+ flexShrink: 0
2392
+ },
2393
+ onClick: removeCurrentModel,
2394
+ children: ""
2395
+ })
2396
+ ]
2397
+ })]
2398
+ }),
2399
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1982
2400
  style: {
1983
- ...S.input,
1984
- width: "100%",
1985
- boxSizing: "border-box"
2401
+ ...S.field,
2402
+ flexDirection: "row",
2403
+ alignItems: "center",
2404
+ gap: 6,
2405
+ gridColumn: "1 / -1"
1986
2406
  },
1987
- value: config.hub_url,
1988
- onChange: (e) => fieldLocal("hub_url", e.target.value),
1989
- onBlur: () => applyPatch({ hub_url: config.hub_url }),
1990
- onKeyDown: (e) => {
1991
- if (e.key === "Enter") e.currentTarget.blur();
1992
- }
1993
- })]
1994
- })
1995
- ] })),
1996
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1997
- style: S.group,
1998
- children: copy.groupMultimodal
2407
+ title: copy.capRestartHint,
2408
+ children: [
2409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2410
+ type: "checkbox",
2411
+ checked: !!config.imagegen_enabled,
2412
+ onChange: (e) => field("imagegen_enabled", e.target.checked)
2413
+ }),
2414
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2415
+ style: { fontSize: 12.5 },
2416
+ children: copy.enableImagegen
2417
+ }),
2418
+ config.imagegen_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2419
+ style: {
2420
+ fontSize: 11,
2421
+ opacity: .55
2422
+ },
2423
+ children: copy.capDisabledNote
2424
+ })
2425
+ ]
2426
+ }),
2427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2428
+ style: S.field,
2429
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2430
+ style: S.fieldLabel,
2431
+ children: copy.imagegenProvider
2432
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2433
+ value: config.imagegen_provider,
2434
+ onChange: (v) => field("imagegen_provider", v),
2435
+ options: imagegenProviderOptions
2436
+ })]
2437
+ })
2438
+ ] }))
1999
2439
  }),
2000
- block(copy.cardMM, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2001
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2002
- style: {
2003
- ...S.field,
2004
- flexDirection: "row",
2005
- alignItems: "center",
2006
- gap: 6,
2007
- gridColumn: "1 / -1"
2008
- },
2009
- title: copy.capRestartHint,
2010
- children: [
2011
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2012
- type: "checkbox",
2013
- checked: !!config.vision_enabled,
2014
- onChange: (e) => field("vision_enabled", e.target.checked)
2015
- }),
2016
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2017
- style: { fontSize: 12.5 },
2018
- children: copy.enableCrewVision
2019
- }),
2020
- config.vision_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2021
- style: {
2022
- fontSize: 11,
2023
- opacity: .55
2024
- },
2025
- children: copy.capDisabledNote
2026
- })
2027
- ]
2028
- }),
2029
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2030
- style: S.field,
2031
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2032
- style: S.fieldLabel,
2033
- children: copy.visionProvider
2034
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2035
- value: config.vision_provider,
2036
- onChange: (p) => {
2037
- const custom = customProviders.find((c) => c.id === p);
2038
- const m = custom ? custom.models?.[0] ?? "default" : (VISION_MODELS[p] ?? ["default"])[0];
2039
- setModelAdd(null);
2040
- setConfig((c) => ({
2041
- ...c,
2042
- vision_provider: p,
2043
- vision_model: m
2044
- }));
2045
- applyPatch({
2046
- vision_provider: p,
2047
- vision_model: m
2048
- });
2049
- if ((p === "grok" || p === "agy") && !dynModels[p]) get(`/vision-models?provider=${p}`).then((r) => {
2050
- if (r.ok) setDynModels((d) => ({
2051
- ...d,
2052
- [p]: r.models
2053
- }));
2054
- }).catch(() => {});
2440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2441
+ sectionId: "providers",
2442
+ title: copy.sectionNames.providers,
2443
+ summary: sectionSummary(copy.providerCount(customProviders.length), testResult?.ok === false && copy.testFail),
2444
+ expanded: !!expandedSections.providers,
2445
+ onToggle: () => toggleSection("providers"),
2446
+ children: block(copy.cardCustomProv, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2447
+ customProviders.length === 0 && provForm === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2448
+ style: {
2449
+ fontSize: 12,
2450
+ opacity: .5
2055
2451
  },
2056
- options: visionProviderOptions
2057
- })]
2058
- }),
2059
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2060
- style: S.field,
2061
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2062
- style: S.fieldLabel,
2063
- children: copy.visionModel
2064
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2452
+ children: copy.noCustomProviders
2453
+ }),
2454
+ customProviders.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2065
2455
  style: {
2066
2456
  display: "flex",
2067
- gap: 6,
2068
- alignItems: "center"
2457
+ alignItems: "center",
2458
+ gap: 8,
2459
+ fontSize: 12.5
2069
2460
  },
2070
2461
  children: [
2071
- modelAdd === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2072
- value: config.vision_model,
2073
- onChange: (v) => field("vision_model", v),
2074
- options: visionModelOptions
2075
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2076
- autoFocus: true,
2462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2463
+ style: { fontWeight: 500 },
2464
+ children: p.name || p.id
2465
+ }),
2466
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2077
2467
  style: {
2078
- ...S.input,
2079
- width: "100%",
2080
- boxSizing: "border-box",
2081
- ...S.mono
2468
+ ...S.chip(false),
2469
+ opacity: .7
2082
2470
  },
2083
- value: modelAdd,
2084
- placeholder: copy.modelPlaceholder,
2085
- onChange: (e) => setModelAdd(e.target.value),
2086
- onBlur: commitModelAdd,
2087
- onKeyDown: (e) => {
2088
- if (e.key === "Enter") e.currentTarget.blur();
2089
- if (e.key === "Escape") setModelAdd(null);
2090
- }
2471
+ children: provType(p) === "api" ? "API" : "CLI"
2472
+ }),
2473
+ canSee(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2474
+ style: S.chip(true),
2475
+ children: copy.capVision
2091
2476
  }),
2092
- modelAdd === null && (config.vision_provider === "grok" || config.vision_provider === "agy") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2093
- type: "button",
2094
- title: copy.refreshModels,
2095
- disabled: busy,
2477
+ canDraw(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2478
+ style: S.chip(true),
2479
+ children: copy.capImagegen
2480
+ }),
2481
+ provType(p) === "api" && !!p.api_key && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2096
2482
  style: {
2097
- ...S.btn,
2098
- padding: "3px 8px",
2099
- flexShrink: 0
2483
+ fontSize: 11.5,
2484
+ opacity: .5
2100
2485
  },
2101
- onClick: () => {
2102
- const p = config.vision_provider;
2103
- setBusy(true);
2104
- get(`/vision-models?provider=${p}&refresh=1`).then((r) => {
2105
- if (r.ok) setDynModels((d) => ({
2106
- ...d,
2107
- [p]: r.models
2108
- }));
2109
- }).catch(() => {}).finally(() => setBusy(false));
2486
+ children: ["key ", copy.keySet]
2487
+ }),
2488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
2489
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2490
+ style: {
2491
+ ...S.btn,
2492
+ padding: "2px 8px"
2110
2493
  },
2111
- children: "↻"
2494
+ title: copy.testTip,
2495
+ disabled: !!testResult?.busy,
2496
+ onClick: () => runProviderTest(`row:${p.id}`, p),
2497
+ children: testResult?.key === `row:${p.id}` && testResult.busy ? copy.testing : copy.testProvider
2112
2498
  }),
2113
- modelAdd === null && config.vision_provider !== "off" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2114
- type: "button",
2115
- title: copy.addModelTip,
2499
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2116
2500
  style: {
2117
2501
  ...S.btn,
2118
- padding: "3px 8px",
2119
- flexShrink: 0
2502
+ padding: "2px 8px"
2120
2503
  },
2121
- onClick: () => setModelAdd(""),
2122
- children: "+"
2504
+ onClick: () => setProvForm({
2505
+ originalId: p.id,
2506
+ name: p.name ?? p.id,
2507
+ type: provType(p),
2508
+ models: (p.models ?? []).join(", "),
2509
+ base_url: p.base_url ?? "",
2510
+ api_key: p.api_key ?? "",
2511
+ imagegen_model: p.imagegen_model ?? "",
2512
+ vision_command: p.vision_command ?? "",
2513
+ imagegen_command: p.imagegen_command ?? ""
2514
+ }),
2515
+ children: copy.edit
2123
2516
  }),
2124
- modelAdd === null && (extraModels[config.vision_provider] ?? []).includes(config.vision_model) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2125
- type: "button",
2126
- title: copy.removeModelTip,
2517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2127
2518
  style: {
2128
2519
  ...S.btn,
2129
- padding: "3px 8px",
2130
- flexShrink: 0
2520
+ padding: "2px 8px",
2521
+ opacity: .75
2522
+ },
2523
+ onClick: () => {
2524
+ if (confirm(copy.confirmDeleteProvider(p.name || p.id))) deleteProvider(p.id);
2131
2525
  },
2132
- onClick: removeCurrentModel,
2133
- children: "−"
2526
+ children: copy.del
2134
2527
  })
2135
2528
  ]
2136
- })]
2137
- }),
2138
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2139
- style: {
2140
- ...S.field,
2141
- flexDirection: "row",
2142
- alignItems: "center",
2143
- gap: 6,
2144
- gridColumn: "1 / -1"
2145
- },
2146
- title: copy.capRestartHint,
2147
- children: [
2148
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2149
- type: "checkbox",
2150
- checked: !!config.imagegen_enabled,
2151
- onChange: (e) => field("imagegen_enabled", e.target.checked)
2152
- }),
2153
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2154
- style: { fontSize: 12.5 },
2155
- children: copy.enableImagegen
2156
- }),
2157
- config.imagegen_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2158
- style: {
2159
- fontSize: 11,
2160
- opacity: .55
2161
- },
2162
- children: copy.capDisabledNote
2163
- })
2164
- ]
2165
- }),
2166
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2167
- style: S.field,
2168
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2169
- style: S.fieldLabel,
2170
- children: copy.imagegenProvider
2171
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2172
- value: config.imagegen_provider,
2173
- onChange: (v) => field("imagegen_provider", v),
2174
- options: imagegenProviderOptions
2175
- })]
2176
- })
2177
- ] })),
2178
- block(copy.cardCustomProv, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2179
- customProviders.length === 0 && provForm === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2180
- style: {
2181
- fontSize: 12,
2182
- opacity: .5
2183
- },
2184
- children: copy.noCustomProviders
2185
- }),
2186
- customProviders.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2187
- style: {
2188
- display: "flex",
2189
- alignItems: "center",
2190
- gap: 8,
2191
- fontSize: 12.5
2192
- },
2193
- children: [
2194
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2195
- style: { fontWeight: 500 },
2196
- children: p.name || p.id
2197
- }),
2198
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2199
- style: {
2200
- ...S.chip(false),
2201
- opacity: .7
2202
- },
2203
- children: provType(p) === "api" ? "API" : "CLI"
2204
- }),
2205
- canSee(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2206
- style: S.chip(true),
2207
- children: copy.capVision
2208
- }),
2209
- canDraw(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2210
- style: S.chip(true),
2211
- children: copy.capImagegen
2212
- }),
2213
- provType(p) === "api" && !!p.api_key && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2214
- style: {
2215
- fontSize: 11.5,
2216
- opacity: .5
2217
- },
2218
- children: ["key ", copy.keySet]
2529
+ }, p.id)),
2530
+ customProviders.some((p) => testResult?.key === `row:${p.id}`) && testReport(testResult.key),
2531
+ provForm === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2532
+ style: S.btn,
2533
+ onClick: () => setProvForm({
2534
+ name: "",
2535
+ type: "api",
2536
+ models: "",
2537
+ base_url: "",
2538
+ api_key: "",
2539
+ imagegen_model: "",
2540
+ vision_command: "",
2541
+ imagegen_command: ""
2219
2542
  }),
2220
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
2221
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2222
- style: {
2223
- ...S.btn,
2224
- padding: "2px 8px"
2225
- },
2226
- title: copy.testTip,
2227
- disabled: !!testResult?.busy,
2228
- onClick: () => runProviderTest(`row:${p.id}`, p),
2229
- children: testResult?.key === `row:${p.id}` && testResult.busy ? copy.testing : copy.testProvider
2230
- }),
2231
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2232
- style: {
2233
- ...S.btn,
2234
- padding: "2px 8px"
2235
- },
2236
- onClick: () => setProvForm({
2237
- originalId: p.id,
2238
- name: p.name ?? p.id,
2239
- type: provType(p),
2240
- models: (p.models ?? []).join(", "),
2241
- base_url: p.base_url ?? "",
2242
- api_key: p.api_key ?? "",
2243
- imagegen_model: p.imagegen_model ?? "",
2244
- vision_command: p.vision_command ?? "",
2245
- imagegen_command: p.imagegen_command ?? ""
2246
- }),
2247
- children: copy.edit
2248
- }),
2249
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2250
- style: {
2251
- ...S.btn,
2252
- padding: "2px 8px",
2253
- opacity: .75
2254
- },
2255
- onClick: () => {
2256
- if (confirm(copy.confirmDeleteProvider(p.name || p.id))) deleteProvider(p.id);
2257
- },
2258
- children: copy.del
2259
- })
2260
- ]
2261
- }, p.id)),
2262
- customProviders.some((p) => testResult?.key === `row:${p.id}`) && testReport(testResult.key),
2263
- provForm === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2264
- style: S.btn,
2265
- onClick: () => setProvForm({
2266
- name: "",
2267
- type: "api",
2268
- models: "",
2269
- base_url: "",
2270
- api_key: "",
2271
- imagegen_model: "",
2272
- vision_command: "",
2273
- imagegen_command: ""
2274
- }),
2275
- children: copy.addProvider
2276
- }) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2277
- style: {
2278
- borderTop: "1px solid rgba(128,128,128,0.16)",
2279
- paddingTop: 10,
2280
- display: "flex",
2281
- flexDirection: "column",
2282
- gap: 10
2283
- },
2284
- children: [
2285
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2286
- style: S.blockGrid,
2287
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2288
- style: S.field,
2289
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2290
- style: S.fieldLabel,
2291
- children: copy.providerName
2292
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2293
- autoFocus: true,
2294
- style: {
2295
- ...S.input,
2296
- width: "100%",
2297
- boxSizing: "border-box"
2298
- },
2299
- value: provForm.name,
2300
- onChange: (e) => setProvForm({
2301
- ...provForm,
2302
- name: e.target.value
2303
- })
2304
- })]
2305
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2306
- style: S.field,
2307
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2308
- style: S.fieldLabel,
2309
- children: copy.providerType
2310
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2311
- value: provForm.type,
2312
- onChange: (v) => setProvForm({
2313
- ...provForm,
2314
- type: v
2315
- }),
2316
- options: [{
2317
- value: "api",
2318
- label: copy.typeApi
2319
- }, {
2320
- value: "cli",
2321
- label: copy.typeCli
2322
- }]
2323
- })]
2324
- })]
2325
- }),
2326
- provForm.type === "api" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2327
- style: S.blockGrid,
2328
- children: [
2329
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2543
+ children: copy.addProvider
2544
+ }) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2545
+ style: {
2546
+ borderTop: "1px solid rgba(128,128,128,0.16)",
2547
+ paddingTop: 10,
2548
+ display: "flex",
2549
+ flexDirection: "column",
2550
+ gap: 10
2551
+ },
2552
+ children: [
2553
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2554
+ style: S.blockGrid,
2555
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2330
2556
  style: S.field,
2331
2557
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2332
2558
  style: S.fieldLabel,
2333
- children: copy.baseUrl
2559
+ children: copy.providerName
2334
2560
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2561
+ autoFocus: true,
2335
2562
  style: {
2336
2563
  ...S.input,
2337
2564
  width: "100%",
2338
- boxSizing: "border-box",
2339
- ...S.mono
2565
+ boxSizing: "border-box"
2340
2566
  },
2341
- value: provForm.base_url,
2342
- placeholder: "https://api.example.com/v1",
2567
+ value: provForm.name,
2343
2568
  onChange: (e) => setProvForm({
2344
2569
  ...provForm,
2345
- base_url: e.target.value
2570
+ name: e.target.value
2346
2571
  })
2347
2572
  })]
2348
- }),
2573
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2574
+ style: S.field,
2575
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2576
+ style: S.fieldLabel,
2577
+ children: copy.providerType
2578
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
2579
+ value: provForm.type,
2580
+ onChange: (v) => setProvForm({
2581
+ ...provForm,
2582
+ type: v
2583
+ }),
2584
+ options: [{
2585
+ value: "api",
2586
+ label: copy.typeApi
2587
+ }, {
2588
+ value: "cli",
2589
+ label: copy.typeCli
2590
+ }]
2591
+ })]
2592
+ })]
2593
+ }),
2594
+ provForm.type === "api" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2595
+ style: S.blockGrid,
2596
+ children: [
2597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2598
+ style: S.field,
2599
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2600
+ style: S.fieldLabel,
2601
+ children: copy.baseUrl
2602
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2603
+ style: {
2604
+ ...S.input,
2605
+ width: "100%",
2606
+ boxSizing: "border-box",
2607
+ ...S.mono
2608
+ },
2609
+ value: provForm.base_url,
2610
+ placeholder: "https://api.example.com/v1",
2611
+ onChange: (e) => setProvForm({
2612
+ ...provForm,
2613
+ base_url: e.target.value
2614
+ })
2615
+ })]
2616
+ }),
2617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2618
+ style: S.field,
2619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2620
+ style: S.fieldLabel,
2621
+ children: copy.apiKey
2622
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2623
+ type: "password",
2624
+ autoComplete: "off",
2625
+ style: {
2626
+ ...S.input,
2627
+ width: "100%",
2628
+ boxSizing: "border-box",
2629
+ ...S.mono
2630
+ },
2631
+ value: provForm.api_key,
2632
+ placeholder: copy.keyPlaceholder,
2633
+ onChange: (e) => setProvForm({
2634
+ ...provForm,
2635
+ api_key: e.target.value
2636
+ })
2637
+ })]
2638
+ }),
2639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2640
+ style: S.field,
2641
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2642
+ style: S.fieldLabel,
2643
+ children: copy.modelsField
2644
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2645
+ style: {
2646
+ ...S.input,
2647
+ width: "100%",
2648
+ boxSizing: "border-box",
2649
+ ...S.mono
2650
+ },
2651
+ value: provForm.models,
2652
+ placeholder: "gpt-4o-mini, qwen-vl-max",
2653
+ onChange: (e) => setProvForm({
2654
+ ...provForm,
2655
+ models: e.target.value
2656
+ })
2657
+ })]
2658
+ }),
2659
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2660
+ style: S.field,
2661
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2662
+ style: S.fieldLabel,
2663
+ children: copy.imagegenModel
2664
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2665
+ style: {
2666
+ ...S.input,
2667
+ width: "100%",
2668
+ boxSizing: "border-box",
2669
+ ...S.mono
2670
+ },
2671
+ value: provForm.imagegen_model,
2672
+ placeholder: "dall-e-3",
2673
+ onChange: (e) => setProvForm({
2674
+ ...provForm,
2675
+ imagegen_model: e.target.value
2676
+ })
2677
+ })]
2678
+ })
2679
+ ]
2680
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2681
+ style: {
2682
+ fontSize: 11.5,
2683
+ opacity: .55
2684
+ },
2685
+ children: copy.apiFormHint
2686
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2349
2687
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2350
2688
  style: S.field,
2351
2689
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2352
2690
  style: S.fieldLabel,
2353
- children: copy.apiKey
2691
+ children: copy.visionCmd
2354
2692
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2355
- type: "password",
2356
- autoComplete: "off",
2357
2693
  style: {
2358
2694
  ...S.input,
2359
2695
  width: "100%",
2360
2696
  boxSizing: "border-box",
2361
2697
  ...S.mono
2362
2698
  },
2363
- value: provForm.api_key,
2364
- placeholder: copy.keyPlaceholder,
2699
+ value: provForm.vision_command,
2700
+ placeholder: "mycli see {image} --ask {question} --model {model}",
2365
2701
  onChange: (e) => setProvForm({
2366
2702
  ...provForm,
2367
- api_key: e.target.value
2703
+ vision_command: e.target.value
2368
2704
  })
2369
2705
  })]
2370
2706
  }),
@@ -2372,7 +2708,7 @@ function WorkersPanel({ ctx }) {
2372
2708
  style: S.field,
2373
2709
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2374
2710
  style: S.fieldLabel,
2375
- children: copy.modelsField
2711
+ children: copy.imagegenCmd
2376
2712
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2377
2713
  style: {
2378
2714
  ...S.input,
@@ -2380,11 +2716,11 @@ function WorkersPanel({ ctx }) {
2380
2716
  boxSizing: "border-box",
2381
2717
  ...S.mono
2382
2718
  },
2383
- value: provForm.models,
2384
- placeholder: "gpt-4o-mini, qwen-vl-max",
2719
+ value: provForm.imagegen_command,
2720
+ placeholder: "mycli gen {prompt} -o {output}",
2385
2721
  onChange: (e) => setProvForm({
2386
2722
  ...provForm,
2387
- models: e.target.value
2723
+ imagegen_command: e.target.value
2388
2724
  })
2389
2725
  })]
2390
2726
  }),
@@ -2392,7 +2728,7 @@ function WorkersPanel({ ctx }) {
2392
2728
  style: S.field,
2393
2729
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2394
2730
  style: S.fieldLabel,
2395
- children: copy.imagegenModel
2731
+ children: copy.modelsField
2396
2732
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2397
2733
  style: {
2398
2734
  ...S.input,
@@ -2400,133 +2736,66 @@ function WorkersPanel({ ctx }) {
2400
2736
  boxSizing: "border-box",
2401
2737
  ...S.mono
2402
2738
  },
2403
- value: provForm.imagegen_model,
2404
- placeholder: "dall-e-3",
2405
- onChange: (e) => setProvForm({
2406
- ...provForm,
2407
- imagegen_model: e.target.value
2408
- })
2409
- })]
2410
- })
2411
- ]
2412
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2413
- style: {
2414
- fontSize: 11.5,
2415
- opacity: .55
2416
- },
2417
- children: copy.apiFormHint
2418
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2419
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2420
- style: S.field,
2421
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2422
- style: S.fieldLabel,
2423
- children: copy.visionCmd
2424
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2425
- style: {
2426
- ...S.input,
2427
- width: "100%",
2428
- boxSizing: "border-box",
2429
- ...S.mono
2430
- },
2431
- value: provForm.vision_command,
2432
- placeholder: "mycli see {image} --ask {question} --model {model}",
2433
- onChange: (e) => setProvForm({
2434
- ...provForm,
2435
- vision_command: e.target.value
2436
- })
2437
- })]
2438
- }),
2439
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2440
- style: S.field,
2441
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2442
- style: S.fieldLabel,
2443
- children: copy.imagegenCmd
2444
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2445
- style: {
2446
- ...S.input,
2447
- width: "100%",
2448
- boxSizing: "border-box",
2449
- ...S.mono
2450
- },
2451
- value: provForm.imagegen_command,
2452
- placeholder: "mycli gen {prompt} -o {output}",
2453
- onChange: (e) => setProvForm({
2454
- ...provForm,
2455
- imagegen_command: e.target.value
2456
- })
2457
- })]
2458
- }),
2459
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2460
- style: S.field,
2461
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2462
- style: S.fieldLabel,
2463
- children: copy.modelsField
2464
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2465
- style: {
2466
- ...S.input,
2467
- width: "100%",
2468
- boxSizing: "border-box",
2469
- ...S.mono
2470
- },
2471
- value: provForm.models,
2472
- placeholder: "model-a, model-b",
2473
- onChange: (e) => setProvForm({
2474
- ...provForm,
2475
- models: e.target.value
2476
- })
2477
- })]
2478
- }),
2479
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2480
- style: {
2481
- fontSize: 11.5,
2482
- opacity: .55
2483
- },
2484
- children: copy.cliFormHint
2485
- })
2486
- ] }),
2487
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2488
- style: {
2489
- display: "flex",
2490
- gap: 8,
2491
- alignItems: "center"
2492
- },
2493
- children: [
2494
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2495
- style: S.btn,
2496
- onClick: saveProviderForm,
2497
- children: copy.saveProvider
2498
- }),
2499
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2500
- style: S.btn,
2501
- title: copy.testTip,
2502
- disabled: !!testResult?.busy,
2503
- onClick: () => runProviderTest("form", {
2504
- id: provForm.originalId ?? "draft",
2505
- name: provForm.name || "draft",
2506
- type: provForm.type,
2507
- models: provForm.models.split(/[,,\n]+/).map((x) => x.trim()).filter(Boolean),
2508
- base_url: provForm.base_url.trim(),
2509
- api_key: provForm.api_key,
2510
- imagegen_model: provForm.imagegen_model.trim(),
2511
- vision_command: provForm.vision_command.trim(),
2512
- imagegen_command: provForm.imagegen_command.trim()
2513
- }),
2514
- children: testResult?.key === "form" && testResult.busy ? copy.testing : copy.testProvider
2739
+ value: provForm.models,
2740
+ placeholder: "model-a, model-b",
2741
+ onChange: (e) => setProvForm({
2742
+ ...provForm,
2743
+ models: e.target.value
2744
+ })
2745
+ })]
2515
2746
  }),
2516
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2517
2748
  style: {
2518
- ...S.btn,
2519
- opacity: .75
2749
+ fontSize: 11.5,
2750
+ opacity: .55
2520
2751
  },
2521
- onClick: () => setProvForm(null),
2522
- children: copy.cancel
2752
+ children: copy.cliFormHint
2523
2753
  })
2524
- ]
2525
- }),
2526
- testReport("form")
2527
- ]
2528
- })
2529
- ] }), false)
2754
+ ] }),
2755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2756
+ style: {
2757
+ display: "flex",
2758
+ gap: 8,
2759
+ alignItems: "center"
2760
+ },
2761
+ children: [
2762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2763
+ style: S.btn,
2764
+ onClick: saveProviderForm,
2765
+ children: copy.saveProvider
2766
+ }),
2767
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2768
+ style: S.btn,
2769
+ title: copy.testTip,
2770
+ disabled: !!testResult?.busy,
2771
+ onClick: () => runProviderTest("form", {
2772
+ id: provForm.originalId ?? "draft",
2773
+ name: provForm.name || "draft",
2774
+ type: provForm.type,
2775
+ models: provForm.models.split(/[,,\n]+/).map((x) => x.trim()).filter(Boolean),
2776
+ base_url: provForm.base_url.trim(),
2777
+ api_key: provForm.api_key,
2778
+ imagegen_model: provForm.imagegen_model.trim(),
2779
+ vision_command: provForm.vision_command.trim(),
2780
+ imagegen_command: provForm.imagegen_command.trim()
2781
+ }),
2782
+ children: testResult?.key === "form" && testResult.busy ? copy.testing : copy.testProvider
2783
+ }),
2784
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2785
+ style: {
2786
+ ...S.btn,
2787
+ opacity: .75
2788
+ },
2789
+ onClick: () => setProvForm(null),
2790
+ children: copy.cancel
2791
+ })
2792
+ ]
2793
+ }),
2794
+ testReport("form")
2795
+ ]
2796
+ })
2797
+ ] }), false)
2798
+ })
2530
2799
  ] }),
2531
2800
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2532
2801
  style: {
@@ -2544,144 +2813,147 @@ function WorkersPanel({ ctx }) {
2544
2813
  },
2545
2814
  children: notice
2546
2815
  }),
2547
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2548
- style: S.section,
2549
- children: copy.jobs
2550
- }),
2551
- jobs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2552
- style: { opacity: .55 },
2553
- children: copy.empty
2554
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2555
- style: { overflowX: "auto" },
2556
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
2557
- style: {
2558
- borderCollapse: "collapse",
2559
- width: "100%",
2560
- tableLayout: "fixed",
2561
- minWidth: 760
2562
- },
2563
- children: [
2564
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("colgroup", { children: [
2565
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 64 } }),
2566
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 58 } }),
2567
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 72 } }),
2568
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 40 } }),
2569
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 100 } }),
2570
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 84 } }),
2571
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 340 } })
2572
- ] }),
2573
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: [
2574
- copy.col.id,
2575
- copy.col.source,
2576
- copy.col.tier,
2577
- copy.col.status,
2578
- copy.col.progress,
2579
- copy.col.tokens,
2580
- copy.col.task
2581
- ].map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2582
- style: {
2583
- ...S.tight,
2584
- fontSize: 11,
2585
- opacity: .55,
2586
- fontWeight: 500
2587
- },
2588
- children: h
2589
- }, h)) }) }),
2590
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: jobs.map((job) => {
2591
- const color = job.status === "running" ? "#4a9eff" : job.status === "done" ? "#3fb950" : "#f85149";
2592
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2593
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2594
- style: {
2595
- ...S.tight,
2596
- ...S.mono
2597
- },
2598
- title: job.id,
2599
- children: String(job.id).replace(/-[a-z0-9]+$/, "")
2600
- }),
2601
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2602
- style: S.tight,
2603
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2604
- style: S.chip(job.source === "claude-code" || job.source === "codex"),
2605
- children: sourceLabel(job.source)
2606
- })
2607
- }),
2608
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
2609
- style: S.tight,
2610
- children: [
2611
- job.tier,
2612
- "/",
2613
- job.effort
2614
- ]
2615
- }),
2616
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2617
- style: {
2618
- ...S.tight,
2619
- textAlign: "center"
2620
- },
2621
- title: `${job.status}${job.currentTool ? ` · ${job.currentTool}` : ""}`,
2622
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2816
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2817
+ sectionId: "jobs",
2818
+ title: copy.sectionNames.jobs,
2819
+ summary: sectionSummary(copy.jobCount(jobs.length), jobs.some((job) => job.status === "running") && copy.runningCount(jobs.filter((job) => job.status === "running").length)),
2820
+ expanded: !!expandedSections.jobs,
2821
+ onToggle: () => toggleSection("jobs"),
2822
+ children: jobs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2823
+ style: { opacity: .55 },
2824
+ children: copy.empty
2825
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2826
+ style: { overflowX: "auto" },
2827
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
2828
+ style: {
2829
+ borderCollapse: "collapse",
2830
+ width: "100%",
2831
+ tableLayout: "fixed",
2832
+ minWidth: 760
2833
+ },
2834
+ children: [
2835
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("colgroup", { children: [
2836
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 64 } }),
2837
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 58 } }),
2838
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 72 } }),
2839
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 40 } }),
2840
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 100 } }),
2841
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 84 } }),
2842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 340 } })
2843
+ ] }),
2844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: [
2845
+ copy.col.id,
2846
+ copy.col.source,
2847
+ copy.col.tier,
2848
+ copy.col.status,
2849
+ copy.col.progress,
2850
+ copy.col.tokens,
2851
+ copy.col.task
2852
+ ].map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2853
+ style: {
2854
+ ...S.tight,
2855
+ fontSize: 11,
2856
+ opacity: .55,
2857
+ fontWeight: 500
2858
+ },
2859
+ children: h
2860
+ }, h)) }) }),
2861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: jobs.map((job) => {
2862
+ const color = job.status === "running" ? "#4a9eff" : job.status === "done" ? "#3fb950" : "#f85149";
2863
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2623
2865
  style: {
2624
- color,
2625
- cursor: "default"
2866
+ ...S.tight,
2867
+ ...S.mono
2626
2868
  },
2627
- children: "●"
2628
- })
2629
- }),
2630
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
2631
- style: {
2632
- ...S.tight,
2633
- ...S.mono
2634
- },
2635
- title: copy.progressTip(elapsed(job.startedAt, job.endedAt), job.turn, job.step, job.toolCalls ?? "-"),
2636
- children: [
2637
- elapsed(job.startedAt, job.endedAt),
2638
- " ",
2639
- job.turn,
2640
- ".",
2641
- job.step,
2642
- " ",
2643
- job.toolCalls ?? "-",
2644
- "t"
2645
- ]
2646
- }),
2647
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2648
- style: {
2649
- ...S.tight,
2650
- ...S.mono
2651
- },
2652
- children: job.tokens ? `${ktok(job.tokens.input)}/${ktok(job.tokens.output)}` : "-"
2653
- }),
2654
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2655
- style: {
2656
- ...S.cell,
2657
- position: "relative"
2658
- },
2659
- onMouseEnter: (e) => {
2660
- const rect = e.currentTarget.getBoundingClientRect();
2661
- setHoverTask({
2662
- id: job.id,
2663
- text: job.task,
2664
- right: Math.max(8, window.innerWidth - rect.right),
2665
- top: rect.bottom + 4,
2666
- bottom: window.innerHeight - rect.top + 4,
2667
- flip: rect.top < 240
2668
- });
2669
- },
2670
- onMouseLeave: () => setHoverTask(null),
2671
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2869
+ title: job.id,
2870
+ children: String(job.id).replace(/-[a-z0-9]+$/, "")
2871
+ }),
2872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2873
+ style: S.tight,
2874
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2875
+ style: S.chip(job.source === "claude-code" || job.source === "codex"),
2876
+ children: sourceLabel(job.source)
2877
+ })
2878
+ }),
2879
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
2880
+ style: S.tight,
2881
+ children: [
2882
+ job.tier,
2883
+ "/",
2884
+ job.effort
2885
+ ]
2886
+ }),
2887
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2888
+ style: {
2889
+ ...S.tight,
2890
+ textAlign: "center"
2891
+ },
2892
+ title: `${job.status}${job.currentTool ? ` · ${job.currentTool}` : ""}`,
2893
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2894
+ style: {
2895
+ color,
2896
+ cursor: "default"
2897
+ },
2898
+ children: "●"
2899
+ })
2900
+ }),
2901
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
2902
+ style: {
2903
+ ...S.tight,
2904
+ ...S.mono
2905
+ },
2906
+ title: copy.progressTip(elapsed(job.startedAt, job.endedAt), job.turn, job.step, job.toolCalls ?? "-"),
2907
+ children: [
2908
+ elapsed(job.startedAt, job.endedAt),
2909
+ " ",
2910
+ job.turn,
2911
+ ".",
2912
+ job.step,
2913
+ " ",
2914
+ job.toolCalls ?? "-",
2915
+ "t"
2916
+ ]
2917
+ }),
2918
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2919
+ style: {
2920
+ ...S.tight,
2921
+ ...S.mono
2922
+ },
2923
+ children: job.tokens ? `${ktok(job.tokens.input)}/${ktok(job.tokens.output)}` : "-"
2924
+ }),
2925
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2672
2926
  style: {
2673
- whiteSpace: "nowrap",
2674
- overflow: "hidden",
2675
- textOverflow: "ellipsis",
2676
- lineHeight: 1.45,
2677
- fontSize: 12.5
2927
+ ...S.cell,
2928
+ position: "relative"
2678
2929
  },
2679
- children: job.task
2930
+ onMouseEnter: (e) => {
2931
+ const rect = e.currentTarget.getBoundingClientRect();
2932
+ setHoverTask({
2933
+ id: job.id,
2934
+ text: job.task,
2935
+ right: Math.max(8, window.innerWidth - rect.right),
2936
+ top: rect.bottom + 4,
2937
+ bottom: window.innerHeight - rect.top + 4,
2938
+ flip: rect.top < 240
2939
+ });
2940
+ },
2941
+ onMouseLeave: () => setHoverTask(null),
2942
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2943
+ style: {
2944
+ whiteSpace: "nowrap",
2945
+ overflow: "hidden",
2946
+ textOverflow: "ellipsis",
2947
+ lineHeight: 1.45,
2948
+ fontSize: 12.5
2949
+ },
2950
+ children: job.task
2951
+ })
2680
2952
  })
2681
- })
2682
- ] }, job.id);
2683
- }) })
2684
- ]
2953
+ ] }, job.id);
2954
+ }) })
2955
+ ]
2956
+ })
2685
2957
  })
2686
2958
  }),
2687
2959
  hoverTask && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -2709,7 +2981,7 @@ function WorkersPanel({ ctx }) {
2709
2981
  ]
2710
2982
  });
2711
2983
  }
2712
- function apply(ctx) {
2984
+ function apply$1(ctx) {
2713
2985
  let runningCount = 0;
2714
2986
  ctx.slots.inject("settings.section", () => {
2715
2987
  let dispose = register();
@@ -2739,7 +3011,7 @@ function apply(ctx) {
2739
3011
  const poll = async () => {
2740
3012
  if (document.visibilityState !== "visible") return;
2741
3013
  try {
2742
- const r = await (await fetch(`${API}/jobs`, { cache: "no-store" })).json();
3014
+ const r = await (await fetch(`${API$1}/jobs`, { cache: "no-store" })).json();
2743
3015
  const n = r.ok ? (r.jobs ?? []).filter((j) => j.status === "running").length : 0;
2744
3016
  if (n !== runningCount) {
2745
3017
  runningCount = n;
@@ -2759,6 +3031,415 @@ function apply(ctx) {
2759
3031
  });
2760
3032
  }
2761
3033
  //#endregion
3034
+ //#region src/client/activation-summary.tsx
3035
+ const ORDER = [
3036
+ "live",
3037
+ "next-workflow",
3038
+ "next-session",
3039
+ "restart-required"
3040
+ ];
3041
+ const LABELS = {
3042
+ zh: {
3043
+ title: "配置生效边界",
3044
+ hint: "这里显示全局 Settings 保存后的实际生效时机;会话内 dsh_worker_config 覆盖可能更早生效。",
3045
+ boundary: {
3046
+ live: "Live · 当前运行时",
3047
+ "next-workflow": "Next workflow · 下一个任务",
3048
+ "next-session": "Next session · 新 CC / Codex 会话",
3049
+ "restart-required": "Restart required · 重启 DSH / MCP"
3050
+ }
3051
+ },
3052
+ en: {
3053
+ title: "Configuration activation boundaries",
3054
+ hint: "Shows when persisted Settings changes actually take effect. Session-level dsh_worker_config overrides may activate earlier.",
3055
+ boundary: {
3056
+ live: "Live · current runtime",
3057
+ "next-workflow": "Next workflow",
3058
+ "next-session": "Next session · new CC / Codex session",
3059
+ "restart-required": "Restart required · restart DSH / MCP"
3060
+ }
3061
+ }
3062
+ };
3063
+ function groupActivationBoundaries(activation = {}) {
3064
+ const grouped = Object.fromEntries(ORDER.map((boundary) => [boundary, []]));
3065
+ for (const [key, entry] of Object.entries(activation)) if (entry?.global && grouped[entry.global]) grouped[entry.global].push(key);
3066
+ for (const boundary of ORDER) grouped[boundary].sort();
3067
+ return grouped;
3068
+ }
3069
+ function ActivationSummary({ activation, locale }) {
3070
+ if (!activation || Object.keys(activation).length === 0) return null;
3071
+ const copy = LABELS[locale === "zh" ? "zh" : "en"];
3072
+ const grouped = groupActivationBoundaries(activation);
3073
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3074
+ style: {
3075
+ border: "1px solid rgba(128,128,128,0.22)",
3076
+ borderRadius: 8,
3077
+ padding: "9px 12px",
3078
+ display: "flex",
3079
+ flexDirection: "column",
3080
+ gap: 5
3081
+ },
3082
+ children: [
3083
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3084
+ style: {
3085
+ fontWeight: 600,
3086
+ fontSize: 12.5
3087
+ },
3088
+ children: copy.title
3089
+ }),
3090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3091
+ style: {
3092
+ fontSize: 11,
3093
+ opacity: .6
3094
+ },
3095
+ children: copy.hint
3096
+ }),
3097
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3098
+ style: {
3099
+ display: "grid",
3100
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
3101
+ gap: "5px 12px"
3102
+ },
3103
+ children: ORDER.map((boundary) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3104
+ style: { minWidth: 0 },
3105
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3106
+ style: {
3107
+ fontSize: 10.5,
3108
+ opacity: .7,
3109
+ fontWeight: 600
3110
+ },
3111
+ children: copy.boundary[boundary]
3112
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3113
+ style: {
3114
+ fontSize: 10.5,
3115
+ opacity: .55,
3116
+ wordBreak: "break-word"
3117
+ },
3118
+ children: grouped[boundary].length ? grouped[boundary].join(" · ") : "—"
3119
+ })]
3120
+ }, boundary))
3121
+ })
3122
+ ]
3123
+ });
3124
+ }
3125
+ //#endregion
3126
+ //#region src/client/entry.tsx
3127
+ const inject = inject$1;
3128
+ const API = "/_dsh/dsh-crew";
3129
+ const DEFAULT_ADAPTIVE = {
3130
+ enabled: false,
3131
+ window_size: 8,
3132
+ min_samples: 2
3133
+ };
3134
+ function clampInt(value, fallback, min, max) {
3135
+ const parsed = Number(value);
3136
+ if (!Number.isInteger(parsed)) return fallback;
3137
+ return Math.max(min, Math.min(max, parsed));
3138
+ }
3139
+ function normalizeAdaptive(value) {
3140
+ const windowSize = clampInt(value?.window_size, DEFAULT_ADAPTIVE.window_size, 1, 32);
3141
+ return {
3142
+ enabled: value?.enabled === true,
3143
+ window_size: windowSize,
3144
+ min_samples: clampInt(value?.min_samples, DEFAULT_ADAPTIVE.min_samples, 1, windowSize)
3145
+ };
3146
+ }
3147
+ function useLocale(ctx) {
3148
+ return (0, react.useSyncExternalStore)((notify) => ctx.on("locale/change", notify), () => ctx.locale.getLocale().active, () => ctx.locale.getLocale().active);
3149
+ }
3150
+ function AdaptiveRoutingPanel({ ctx }) {
3151
+ const zh = useLocale(ctx) === "zh";
3152
+ const [adaptive, setAdaptive] = (0, react.useState)(DEFAULT_ADAPTIVE);
3153
+ const [loaded, setLoaded] = (0, react.useState)(false);
3154
+ const [saving, setSaving] = (0, react.useState)(false);
3155
+ const [message, setMessage] = (0, react.useState)("");
3156
+ const copy = zh ? {
3157
+ title: "自适应模型路由(实验)",
3158
+ hint: "默认关闭。仅对系统自动产生的候选做健康排序;显式 Provider / Model 优先级永远保持原序。信号只来自本进程内 Crew 已观察到的成功、失败、超时与粗粒度延迟,不读取额度、价格或凭据。重启 Hub 会清空健康历史。",
3159
+ enabled: "启用自适应路由",
3160
+ window: "健康窗口",
3161
+ minSamples: "最少样本",
3162
+ windowHint: "每个 role/provider/model 最多参考最近 1–32 次结果。",
3163
+ minHint: "达到该样本数后才允许健康分数影响自动候选顺序。",
3164
+ boundary: "生效边界:下一工作流",
3165
+ saved: "已保存",
3166
+ loading: "加载中…"
3167
+ } : {
3168
+ title: "Adaptive Model Routing (experimental)",
3169
+ hint: "Off by default. Health ordering applies only to automatically derived candidates; explicit Provider / Model priorities always keep their order. Signals are limited to Crew-observed success, failure, timeout, and coarse latency in this process—never quota, pricing, or credentials. Restarting the Hub clears the history.",
3170
+ enabled: "Enable adaptive routing",
3171
+ window: "Health window",
3172
+ minSamples: "Minimum samples",
3173
+ windowHint: "Use at most the most recent 1–32 outcomes per role/provider/model.",
3174
+ minHint: "Health may affect automatic candidate order only after this many samples.",
3175
+ boundary: "Activation boundary: next workflow",
3176
+ saved: "Saved",
3177
+ loading: "Loading…"
3178
+ };
3179
+ (0, react.useEffect)(() => {
3180
+ let cancelled = false;
3181
+ const load = async () => {
3182
+ try {
3183
+ const res = await fetch(`${API}/config?lang=${zh ? "zh" : "en"}`, { cache: "no-store" });
3184
+ const body = await res.json();
3185
+ if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
3186
+ if (!cancelled) {
3187
+ setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
3188
+ setMessage("");
3189
+ setLoaded(true);
3190
+ }
3191
+ } catch (err) {
3192
+ if (!cancelled) {
3193
+ setMessage(err?.message ?? String(err));
3194
+ setLoaded(true);
3195
+ }
3196
+ }
3197
+ };
3198
+ load();
3199
+ return () => {
3200
+ cancelled = true;
3201
+ };
3202
+ }, [zh]);
3203
+ const save = async (candidate) => {
3204
+ const next = normalizeAdaptive(candidate);
3205
+ setAdaptive(next);
3206
+ setSaving(true);
3207
+ try {
3208
+ const res = await fetch(`${API}/config?lang=${zh ? "zh" : "en"}`, {
3209
+ method: "POST",
3210
+ headers: { "content-type": "application/json" },
3211
+ body: JSON.stringify({ worker: { model_policy: { adaptive: next } } })
3212
+ });
3213
+ const body = await res.json();
3214
+ if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
3215
+ setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
3216
+ setMessage(copy.saved);
3217
+ setTimeout(() => setMessage(""), 1500);
3218
+ } catch (err) {
3219
+ setMessage(err?.message ?? String(err));
3220
+ } finally {
3221
+ setSaving(false);
3222
+ }
3223
+ };
3224
+ if (!loaded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3225
+ style: {
3226
+ fontSize: 12,
3227
+ opacity: .6
3228
+ },
3229
+ children: copy.loading
3230
+ });
3231
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3232
+ style: {
3233
+ display: "flex",
3234
+ flexDirection: "column",
3235
+ gap: 10,
3236
+ fontSize: 13,
3237
+ lineHeight: 1.55
3238
+ },
3239
+ children: [
3240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3241
+ style: {
3242
+ fontWeight: 600,
3243
+ fontSize: 14
3244
+ },
3245
+ children: copy.title
3246
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3247
+ style: {
3248
+ opacity: .68,
3249
+ fontSize: 12,
3250
+ marginTop: 2
3251
+ },
3252
+ children: copy.hint
3253
+ })] }),
3254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3255
+ style: {
3256
+ display: "flex",
3257
+ alignItems: "center",
3258
+ gap: 7
3259
+ },
3260
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3261
+ type: "checkbox",
3262
+ checked: adaptive.enabled,
3263
+ disabled: saving,
3264
+ onChange: (event) => {
3265
+ save({
3266
+ ...adaptive,
3267
+ enabled: event.target.checked
3268
+ });
3269
+ }
3270
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.enabled })]
3271
+ }),
3272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3273
+ style: {
3274
+ display: "grid",
3275
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
3276
+ gap: 10
3277
+ },
3278
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3279
+ style: {
3280
+ display: "flex",
3281
+ flexDirection: "column",
3282
+ gap: 4
3283
+ },
3284
+ children: [
3285
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3286
+ style: {
3287
+ fontSize: 12,
3288
+ fontWeight: 500
3289
+ },
3290
+ children: copy.window
3291
+ }),
3292
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3293
+ type: "number",
3294
+ min: 1,
3295
+ max: 32,
3296
+ value: adaptive.window_size,
3297
+ disabled: saving,
3298
+ onChange: (event) => {
3299
+ const windowSize = clampInt(event.target.value, adaptive.window_size, 1, 32);
3300
+ setAdaptive((current) => ({
3301
+ ...current,
3302
+ window_size: windowSize,
3303
+ min_samples: Math.min(current.min_samples, windowSize)
3304
+ }));
3305
+ },
3306
+ onBlur: () => {
3307
+ save(adaptive);
3308
+ },
3309
+ style: {
3310
+ padding: "5px 7px",
3311
+ borderRadius: 5,
3312
+ border: "1px solid rgba(128,128,128,0.35)",
3313
+ background: "transparent",
3314
+ color: "inherit"
3315
+ }
3316
+ }),
3317
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3318
+ style: {
3319
+ fontSize: 10.5,
3320
+ opacity: .55
3321
+ },
3322
+ children: copy.windowHint
3323
+ })
3324
+ ]
3325
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3326
+ style: {
3327
+ display: "flex",
3328
+ flexDirection: "column",
3329
+ gap: 4
3330
+ },
3331
+ children: [
3332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3333
+ style: {
3334
+ fontSize: 12,
3335
+ fontWeight: 500
3336
+ },
3337
+ children: copy.minSamples
3338
+ }),
3339
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3340
+ type: "number",
3341
+ min: 1,
3342
+ max: adaptive.window_size,
3343
+ value: adaptive.min_samples,
3344
+ disabled: saving,
3345
+ onChange: (event) => setAdaptive((current) => ({
3346
+ ...current,
3347
+ min_samples: clampInt(event.target.value, current.min_samples, 1, current.window_size)
3348
+ })),
3349
+ onBlur: () => {
3350
+ save(adaptive);
3351
+ },
3352
+ style: {
3353
+ padding: "5px 7px",
3354
+ borderRadius: 5,
3355
+ border: "1px solid rgba(128,128,128,0.35)",
3356
+ background: "transparent",
3357
+ color: "inherit"
3358
+ }
3359
+ }),
3360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3361
+ style: {
3362
+ fontSize: 10.5,
3363
+ opacity: .55
3364
+ },
3365
+ children: copy.minHint
3366
+ })
3367
+ ]
3368
+ })]
3369
+ }),
3370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3371
+ style: {
3372
+ display: "flex",
3373
+ gap: 8,
3374
+ alignItems: "center",
3375
+ fontSize: 11.5,
3376
+ opacity: .62
3377
+ },
3378
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.boundary }), message && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", message] })]
3379
+ })
3380
+ ]
3381
+ });
3382
+ }
3383
+ function ActivationBoundaryPanel({ ctx }) {
3384
+ const locale = useLocale(ctx);
3385
+ const [activation, setActivation] = (0, react.useState)(null);
3386
+ const [error, setError] = (0, react.useState)("");
3387
+ (0, react.useEffect)(() => {
3388
+ let cancelled = false;
3389
+ const load = async () => {
3390
+ try {
3391
+ const res = await fetch(`${API}/config?lang=${locale === "zh" ? "zh" : "en"}`, { cache: "no-store" });
3392
+ const body = await res.json();
3393
+ if (!cancelled) {
3394
+ if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
3395
+ setActivation(body?.config?.config_activation ?? null);
3396
+ setError("");
3397
+ }
3398
+ } catch (err) {
3399
+ if (!cancelled) setError(err?.message ?? String(err));
3400
+ }
3401
+ };
3402
+ load();
3403
+ return () => {
3404
+ cancelled = true;
3405
+ };
3406
+ }, [locale]);
3407
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3408
+ style: {
3409
+ display: "flex",
3410
+ flexDirection: "column",
3411
+ gap: 8,
3412
+ fontSize: 13,
3413
+ lineHeight: 1.55
3414
+ },
3415
+ children: [error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3416
+ style: {
3417
+ fontSize: 11.5,
3418
+ opacity: .6
3419
+ },
3420
+ children: error
3421
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationSummary, {
3422
+ activation: activation ?? void 0,
3423
+ locale
3424
+ })]
3425
+ });
3426
+ }
3427
+ function apply(ctx) {
3428
+ apply$1(ctx);
3429
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
3430
+ name: "settings.section",
3431
+ id: "dsh-crew-adaptive-routing",
3432
+ order: 65,
3433
+ label: () => ctx.locale.getLocale().active === "zh" ? "DSH Crew · 自适应路由" : "DSH Crew · Adaptive Routing"
3434
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdaptiveRoutingPanel, { ctx })));
3435
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
3436
+ name: "settings.section",
3437
+ id: "dsh-crew-runtime-controls",
3438
+ order: 66,
3439
+ label: () => ctx.locale.getLocale().active === "zh" ? "DSH Crew · 生效边界" : "DSH Crew · Activation"
3440
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationBoundaryPanel, { ctx })));
3441
+ }
3442
+ //#endregion
2762
3443
  exports.apply = apply;
2763
3444
  exports.inject = inject;
2764
3445