@ran-sh/dsh-crew 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/README.zh.md +12 -7
- package/docs/installation.md +1 -1
- package/docs/ui-surfaces.md +65 -46
- package/lib/client.js +345 -22
- package/official-web-bridge/lib/client.js +345 -22
- package/package.json +1 -1
- package/src/client/collapsible-sections.mjs +3 -2
- package/src/client/index.tsx +227 -51
- package/src/config-readiness.mjs +56 -5
- package/src/extension-contract.mjs +9 -5
- package/src/hub/index.mjs +854 -445
- package/src/hub-client.mjs +24 -4
- package/src/hub-compatibility.mjs +23 -7
- package/src/install/install.mjs +4 -4
- package/src/install/npx-lifecycle.mjs +263 -118
- package/src/job-contracts.mjs +19 -3
- package/src/jobs.mjs +2 -4
- package/src/local-request-guard.mjs +60 -0
- package/src/mcp-runtime.mjs +7 -5
- package/src/model-routing.mjs +141 -49
- package/src/multimodal.mjs +0 -0
- package/src/official-web-bridge.mjs +242 -84
- package/src/policy.mjs +62 -31
- package/src/provider-config-scrub.mjs +71 -0
- package/src/provider-delete-adapters.mjs +424 -0
- package/src/provider-health.mjs +124 -0
- package/src/provider-inventory.mjs +132 -0
- package/src/provider-lifecycle-state.mjs +81 -0
- package/src/provider-lifecycle.mjs +214 -0
- package/src/provider-profile-store.mjs +171 -0
- package/src/readiness-matrix.mjs +11 -5
- package/src/removable-waiter.mjs +29 -0
- package/src/runtime-identity.mjs +43 -13
- package/src/server.mjs +34 -16
- package/src/status-shard.mjs +13 -2
- package/src/workflow-runtime.mjs +44 -10
- package/windows/start-dsh-crew.cmd +3 -3
- package/windows/start-dsh-crew.ps1 +49 -5
|
@@ -38,6 +38,7 @@ const SETTINGS_SECTION_IDS = Object.freeze([
|
|
|
38
38
|
"adaptive",
|
|
39
39
|
"runtime",
|
|
40
40
|
"multimodal",
|
|
41
|
+
"harnessProviders",
|
|
41
42
|
"providers",
|
|
42
43
|
"jobs"
|
|
43
44
|
]);
|
|
@@ -375,7 +376,8 @@ const COPY = {
|
|
|
375
376
|
adaptive: "自适应路由",
|
|
376
377
|
runtime: "运行 / 生效边界",
|
|
377
378
|
multimodal: "视觉与生图",
|
|
378
|
-
|
|
379
|
+
harnessProviders: "Harness Providers",
|
|
380
|
+
providers: "多模态适配器",
|
|
379
381
|
jobs: "任务状态"
|
|
380
382
|
},
|
|
381
383
|
openHarness: "打开 3210 Crew Harness →",
|
|
@@ -444,6 +446,8 @@ const COPY = {
|
|
|
444
446
|
notAdvertised: "当前未列出",
|
|
445
447
|
noPriority: "未配置优先项",
|
|
446
448
|
modelPoolSummary: (p, m) => `Providers: ${p} · Models: ${m}`,
|
|
449
|
+
healthUnprobed: "UNPROBED",
|
|
450
|
+
checkedAgo: (s) => `${s} 秒前检查`,
|
|
447
451
|
partialCatalog: "部分 Provider 读取失败",
|
|
448
452
|
catalogFailed: "无法读取 Harness 模型目录",
|
|
449
453
|
removePriority: "移除",
|
|
@@ -491,6 +495,19 @@ const COPY = {
|
|
|
491
495
|
visionProvider: "视觉 provider",
|
|
492
496
|
visionModel: "视觉模型",
|
|
493
497
|
imagegenProvider: "生图 provider",
|
|
498
|
+
harnessProviders: "Harness Providers",
|
|
499
|
+
harnessProviderHint: "3210 Crew Harness 的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。",
|
|
500
|
+
providerState: (state) => `状态:${state}`,
|
|
501
|
+
providerModels: (count) => `${count} 个模型`,
|
|
502
|
+
providerJobs: (count) => `${count} 个运行任务`,
|
|
503
|
+
providerDeletePlan: "生成删除计划",
|
|
504
|
+
providerDelete: "确认删除",
|
|
505
|
+
providerPlanReady: "计划已生成;再次确认后才会修改 3210 配置。",
|
|
506
|
+
providerReplacement: "替换 Harness 默认 Provider(输入 Provider id)",
|
|
507
|
+
providerDeleteConfirm: "确认按计划删除这个 Harness Provider?会清理路由引用并重启 3210。",
|
|
508
|
+
providerDeleteBlocked: "当前 Provider 不能删除(内置、在用、已删除或缺少可用替换)。",
|
|
509
|
+
providerLifecycleError: "Provider 生命周期操作失败",
|
|
510
|
+
providerNoInventory: "暂时无法读取 3210 Provider inventory。",
|
|
494
511
|
customProviders: "自定义 Provider",
|
|
495
512
|
addProvider: "+ 添加 Provider",
|
|
496
513
|
noCustomProviders: "暂无。添加后会出现在上方的视觉 / 生图 provider 选择里。",
|
|
@@ -544,6 +561,23 @@ const COPY = {
|
|
|
544
561
|
roleStateHint: "auto:orchestrator 可自动派发;manual:仅显式点名;disabled:禁用。未显式设置时由协作模式推导。",
|
|
545
562
|
autoReview: "自动复审",
|
|
546
563
|
autoReviewHint: "worker 成功后自动运行一次 reviewer(独立模型策略,只读)",
|
|
564
|
+
modelOrdering: "候选排序",
|
|
565
|
+
orderingDesc: {
|
|
566
|
+
manual: "手动顺序",
|
|
567
|
+
"health-aware": "健康感知"
|
|
568
|
+
},
|
|
569
|
+
orderingHint: "手动 priority 始终保持顺序;健康感知仅重排未显式锁定的自动候选。",
|
|
570
|
+
healthGate: "健康门",
|
|
571
|
+
healthGateDesc: {
|
|
572
|
+
"hard-failures": "跳过明确不可调用",
|
|
573
|
+
off: "关闭健康门"
|
|
574
|
+
},
|
|
575
|
+
reviewGate: "Reviewer 验收门",
|
|
576
|
+
reviewGateDesc: {
|
|
577
|
+
required: "required · 未批准则失败",
|
|
578
|
+
optional: "optional · 记录但不阻断",
|
|
579
|
+
off: "off · 不自动复审"
|
|
580
|
+
},
|
|
547
581
|
isolation: "工作区隔离",
|
|
548
582
|
isolationHint: "worktree 为默认;非 Git 仓库时需显式 shared,否则任务以 NOT_GIT_REPOSITORY 失败。",
|
|
549
583
|
isolationDesc: {
|
|
@@ -570,9 +604,8 @@ const COPY = {
|
|
|
570
604
|
d: "接入自己的 API 或本地命令;保存后会出现在上面的视觉 / 生图 provider 选择里。"
|
|
571
605
|
},
|
|
572
606
|
modeDesc: {
|
|
573
|
-
auto: "auto · 优先
|
|
574
|
-
hub: "hub · 必须
|
|
575
|
-
standalone: "standalone · 独立进程"
|
|
607
|
+
auto: "auto · 优先 3210",
|
|
608
|
+
hub: "hub · 必须 3210"
|
|
576
609
|
},
|
|
577
610
|
save: "保存",
|
|
578
611
|
saved: "已保存",
|
|
@@ -654,7 +687,8 @@ const COPY = {
|
|
|
654
687
|
adaptive: "Adaptive routing",
|
|
655
688
|
runtime: "Runtime / activation boundaries",
|
|
656
689
|
multimodal: "Vision & image generation",
|
|
657
|
-
|
|
690
|
+
harnessProviders: "Harness Providers",
|
|
691
|
+
providers: "Multimodal adapters",
|
|
658
692
|
jobs: "Task status"
|
|
659
693
|
},
|
|
660
694
|
openHarness: "Open 3210 Crew Harness →",
|
|
@@ -723,6 +757,8 @@ const COPY = {
|
|
|
723
757
|
notAdvertised: "Currently not advertised",
|
|
724
758
|
noPriority: "No priority models configured",
|
|
725
759
|
modelPoolSummary: (p, m) => `Providers: ${p} · Models: ${m}`,
|
|
760
|
+
healthUnprobed: "UNPROBED",
|
|
761
|
+
checkedAgo: (s) => `checked ${s}s ago`,
|
|
726
762
|
partialCatalog: "Some providers failed to load",
|
|
727
763
|
catalogFailed: "Unable to read Harness model catalog",
|
|
728
764
|
removePriority: "Remove",
|
|
@@ -770,6 +806,19 @@ const COPY = {
|
|
|
770
806
|
visionProvider: "Vision provider",
|
|
771
807
|
visionModel: "Vision model",
|
|
772
808
|
imagegenProvider: "Image-gen provider",
|
|
809
|
+
harnessProviders: "Harness Providers",
|
|
810
|
+
harnessProviderHint: "Live Provider registration and lifecycle in the 3210 Crew Harness; separate from vision / image-gen adapters.",
|
|
811
|
+
providerState: (state) => `State: ${state}`,
|
|
812
|
+
providerModels: (count) => `${count} models`,
|
|
813
|
+
providerJobs: (count) => `${count} running jobs`,
|
|
814
|
+
providerDeletePlan: "Plan deletion",
|
|
815
|
+
providerDelete: "Confirm delete",
|
|
816
|
+
providerPlanReady: "Plan ready; a second confirmation is required before changing 3210 config.",
|
|
817
|
+
providerReplacement: "Replacement Harness Default provider id",
|
|
818
|
+
providerDeleteConfirm: "Delete this Harness Provider per the plan? Routing refs will be scrubbed and 3210 restarted.",
|
|
819
|
+
providerDeleteBlocked: "This Provider cannot be deleted (built-in, in use, already absent, or missing a valid replacement).",
|
|
820
|
+
providerLifecycleError: "Provider lifecycle operation failed",
|
|
821
|
+
providerNoInventory: "3210 Provider inventory is temporarily unavailable.",
|
|
773
822
|
customProviders: "Custom providers",
|
|
774
823
|
addProvider: "+ Add provider",
|
|
775
824
|
noCustomProviders: "None yet. Added providers appear in the vision / image-gen selects above.",
|
|
@@ -823,6 +872,23 @@ const COPY = {
|
|
|
823
872
|
roleStateHint: "auto = the orchestrator may dispatch automatically; manual = only when explicitly named; disabled = off. When unset, derived from the collaboration mode.",
|
|
824
873
|
autoReview: "Automatic review",
|
|
825
874
|
autoReviewHint: "Run one reviewer pass (independent model policy, read-only) after a successful worker run",
|
|
875
|
+
modelOrdering: "Candidate ordering",
|
|
876
|
+
orderingDesc: {
|
|
877
|
+
manual: "Manual order",
|
|
878
|
+
"health-aware": "Health-aware"
|
|
879
|
+
},
|
|
880
|
+
orderingHint: "Explicit priority always keeps its order; health-aware mode only reorders automatic, unlocked candidates.",
|
|
881
|
+
healthGate: "Health gate",
|
|
882
|
+
healthGateDesc: {
|
|
883
|
+
"hard-failures": "Skip known hard failures",
|
|
884
|
+
off: "Health gate off"
|
|
885
|
+
},
|
|
886
|
+
reviewGate: "Reviewer acceptance gate",
|
|
887
|
+
reviewGateDesc: {
|
|
888
|
+
required: "required · fail unless approved",
|
|
889
|
+
optional: "optional · record without blocking",
|
|
890
|
+
off: "off · no automatic review"
|
|
891
|
+
},
|
|
826
892
|
isolation: "Workspace isolation",
|
|
827
893
|
isolationHint: "worktree is the default; non-git workspaces require explicit shared, otherwise jobs fail with NOT_GIT_REPOSITORY.",
|
|
828
894
|
isolationDesc: {
|
|
@@ -849,9 +915,8 @@ const COPY = {
|
|
|
849
915
|
d: "Bring your own API or local command; saved providers appear in the vision / image-gen selects above."
|
|
850
916
|
},
|
|
851
917
|
modeDesc: {
|
|
852
|
-
auto: "auto · prefer
|
|
853
|
-
hub: "hub · require
|
|
854
|
-
standalone: "standalone"
|
|
918
|
+
auto: "auto · prefer 3210",
|
|
919
|
+
hub: "hub · require 3210"
|
|
855
920
|
},
|
|
856
921
|
save: "Save",
|
|
857
922
|
saved: "Saved",
|
|
@@ -1339,6 +1404,10 @@ function WorkersPanel({ ctx }) {
|
|
|
1339
1404
|
const [modelCatalog, setModelCatalog] = (0, react.useState)(null);
|
|
1340
1405
|
const [modelCatalogError, setModelCatalogError] = (0, react.useState)("");
|
|
1341
1406
|
const [modelCatalogBusy, setModelCatalogBusy] = (0, react.useState)(false);
|
|
1407
|
+
const [providerInventory, setProviderInventory] = (0, react.useState)(null);
|
|
1408
|
+
const [providerHealth, setProviderHealth] = (0, react.useState)([]);
|
|
1409
|
+
const [providerInventoryError, setProviderInventoryError] = (0, react.useState)("");
|
|
1410
|
+
const [providerLifecycleBusy, setProviderLifecycleBusy] = (0, react.useState)(null);
|
|
1342
1411
|
const [modelPickerTier, setModelPickerTier] = (0, react.useState)(null);
|
|
1343
1412
|
const [modelQuery, setModelQuery] = (0, react.useState)("");
|
|
1344
1413
|
const [testResult, setTestResult] = (0, react.useState)(null);
|
|
@@ -1362,6 +1431,9 @@ function WorkersPanel({ ctx }) {
|
|
|
1362
1431
|
"pro"
|
|
1363
1432
|
]));
|
|
1364
1433
|
}, [modelCatalogError]);
|
|
1434
|
+
(0, react.useEffect)(() => {
|
|
1435
|
+
if (providerInventoryError) setExpandedSections((current) => openSections(current, ["harnessProviders"]));
|
|
1436
|
+
}, [providerInventoryError]);
|
|
1365
1437
|
(0, react.useEffect)(() => {
|
|
1366
1438
|
if (testResult && !testResult.busy && testResult.ok === false) setExpandedSections((current) => openSections(current, ["providers"]));
|
|
1367
1439
|
}, [testResult]);
|
|
@@ -1422,11 +1494,13 @@ function WorkersPanel({ ctx }) {
|
|
|
1422
1494
|
}, [detectSurface]);
|
|
1423
1495
|
const refreshAll = (0, react.useCallback)(async () => {
|
|
1424
1496
|
try {
|
|
1425
|
-
const [j, s, c, pr] = await Promise.all([
|
|
1497
|
+
const [j, s, c, pr, pi, ph] = await Promise.all([
|
|
1426
1498
|
get("/jobs"),
|
|
1427
1499
|
get("/install/status"),
|
|
1428
1500
|
get("/config"),
|
|
1429
|
-
get("/presets")
|
|
1501
|
+
get("/presets"),
|
|
1502
|
+
get("/providers").catch(() => null),
|
|
1503
|
+
get("/provider-health").catch(() => null)
|
|
1430
1504
|
]);
|
|
1431
1505
|
if (j.ok) setJobs(j.jobs ?? []);
|
|
1432
1506
|
if (s.ok) setStatus(s.status);
|
|
@@ -1438,14 +1512,17 @@ function WorkersPanel({ ctx }) {
|
|
|
1438
1512
|
value: x.id,
|
|
1439
1513
|
label: x.name ?? x.id
|
|
1440
1514
|
}))]);
|
|
1515
|
+
if (pi?.ok) setProviderInventory(pi);
|
|
1516
|
+
if (ph?.ok) setProviderHealth(ph.health ?? []);
|
|
1441
1517
|
} catch {}
|
|
1442
1518
|
}, [get]);
|
|
1443
1519
|
(0, react.useEffect)(() => {
|
|
1444
1520
|
if (surface !== CREW_UI_SURFACES.OFFICIAL) return void 0;
|
|
1445
1521
|
refreshAll();
|
|
1446
1522
|
const timer = setInterval(() => {
|
|
1447
|
-
get("/jobs").then((j) => {
|
|
1523
|
+
Promise.all([get("/jobs"), get("/provider-health").catch(() => null)]).then(([j, health]) => {
|
|
1448
1524
|
if (j.ok) setJobs(j.jobs ?? []);
|
|
1525
|
+
if (health?.ok) setProviderHealth(health.health ?? []);
|
|
1449
1526
|
}).catch(() => {});
|
|
1450
1527
|
}, 3e3);
|
|
1451
1528
|
return () => clearInterval(timer);
|
|
@@ -1467,9 +1544,23 @@ function WorkersPanel({ ctx }) {
|
|
|
1467
1544
|
setModelCatalogBusy(false);
|
|
1468
1545
|
}
|
|
1469
1546
|
}, [get, copy]);
|
|
1547
|
+
const refreshProviderInventory = (0, react.useCallback)(async () => {
|
|
1548
|
+
setProviderInventoryError("");
|
|
1549
|
+
try {
|
|
1550
|
+
const [result, health] = await Promise.all([get("/providers"), get("/provider-health").catch(() => null)]);
|
|
1551
|
+
if (!result.ok) throw new Error(result.error ?? copy.providerNoInventory);
|
|
1552
|
+
setProviderInventory(result);
|
|
1553
|
+
if (health?.ok) setProviderHealth(health.health ?? []);
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
setProviderInventoryError(error?.message ?? copy.providerNoInventory);
|
|
1556
|
+
}
|
|
1557
|
+
}, [get, copy]);
|
|
1470
1558
|
(0, react.useEffect)(() => {
|
|
1471
1559
|
if (surface === CREW_UI_SURFACES.OFFICIAL) refreshHarnessModels();
|
|
1472
1560
|
}, [surface, refreshHarnessModels]);
|
|
1561
|
+
(0, react.useEffect)(() => {
|
|
1562
|
+
if (surface === CREW_UI_SURFACES.OFFICIAL) refreshProviderInventory();
|
|
1563
|
+
}, [surface, refreshProviderInventory]);
|
|
1473
1564
|
const act = (0, react.useCallback)(async (target, confirmName) => {
|
|
1474
1565
|
if (confirmName && !window.confirm(copy.confirmRestore(confirmName))) return;
|
|
1475
1566
|
setBusy(true);
|
|
@@ -1524,6 +1615,36 @@ function WorkersPanel({ ctx }) {
|
|
|
1524
1615
|
...c,
|
|
1525
1616
|
[key]: value
|
|
1526
1617
|
}));
|
|
1618
|
+
const rolePolicyField = (role, patch) => {
|
|
1619
|
+
const effectivePatch = role === "worker" && (patch.ordering === "manual" || patch.ordering === "health-aware") ? {
|
|
1620
|
+
...patch,
|
|
1621
|
+
adaptive: {
|
|
1622
|
+
...normalizeAdaptive(config?.worker?.model_policy?.adaptive),
|
|
1623
|
+
enabled: patch.ordering === "health-aware"
|
|
1624
|
+
}
|
|
1625
|
+
} : patch;
|
|
1626
|
+
setConfig((current) => ({
|
|
1627
|
+
...current,
|
|
1628
|
+
[role]: {
|
|
1629
|
+
...current?.[role],
|
|
1630
|
+
model_policy: {
|
|
1631
|
+
...current?.[role]?.model_policy,
|
|
1632
|
+
...effectivePatch
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}));
|
|
1636
|
+
applyPatch({ [role]: { model_policy: effectivePatch } });
|
|
1637
|
+
};
|
|
1638
|
+
const reviewField = (patch) => {
|
|
1639
|
+
setConfig((current) => ({
|
|
1640
|
+
...current,
|
|
1641
|
+
review: {
|
|
1642
|
+
...current?.review,
|
|
1643
|
+
...patch
|
|
1644
|
+
}
|
|
1645
|
+
}));
|
|
1646
|
+
applyPatch({ review: patch });
|
|
1647
|
+
};
|
|
1527
1648
|
const customProviders = config?.custom_providers ?? [];
|
|
1528
1649
|
const extraModels = config?.extra_models ?? {};
|
|
1529
1650
|
const RESERVED_IDS = [
|
|
@@ -1748,6 +1869,59 @@ function WorkersPanel({ ctx }) {
|
|
|
1748
1869
|
}));
|
|
1749
1870
|
applyPatch(patch);
|
|
1750
1871
|
};
|
|
1872
|
+
const deleteHarnessProvider = async (record) => {
|
|
1873
|
+
if (!record || record.ownership === "harness" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0) {
|
|
1874
|
+
setNotice(copy.providerDeleteBlocked);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
let replacementDefault;
|
|
1878
|
+
if (record.references?.harness_default === true) {
|
|
1879
|
+
const candidate = (providerInventory?.records ?? []).find((item) => item.id !== record.id && item.desired_state !== "absent" && item.ownership !== "harness" && (item.models?.length ?? 0) > 0)?.id ?? "";
|
|
1880
|
+
const entered = window.prompt(copy.providerReplacement, candidate);
|
|
1881
|
+
if (!entered?.trim()) return;
|
|
1882
|
+
replacementDefault = entered.trim();
|
|
1883
|
+
}
|
|
1884
|
+
setProviderLifecycleBusy(record.id);
|
|
1885
|
+
setNotice(copy.working);
|
|
1886
|
+
try {
|
|
1887
|
+
const encoded = encodeURIComponent(record.id);
|
|
1888
|
+
const planned = await post(`/providers/${encoded}/delete-plan`, replacementDefault ? { replacement_default: replacementDefault } : {});
|
|
1889
|
+
if (!planned.ok || !planned.plan) throw new Error(planned.code ?? copy.providerLifecycleError);
|
|
1890
|
+
if (!window.confirm(`${copy.providerPlanReady}\n\n${copy.providerDeleteConfirm}\n${(planned.plan.will_remove ?? []).join("\n")}`)) return;
|
|
1891
|
+
const path = `/providers/${encoded}`;
|
|
1892
|
+
const result = await readJson(await fetch(withLang(path), {
|
|
1893
|
+
method: "DELETE",
|
|
1894
|
+
headers: { "content-type": "application/json" },
|
|
1895
|
+
body: JSON.stringify({
|
|
1896
|
+
plan_id: planned.plan.plan_id,
|
|
1897
|
+
expected_revision: planned.plan.expected_revision,
|
|
1898
|
+
confirm: true,
|
|
1899
|
+
lang: locale === "zh" ? "zh" : "en"
|
|
1900
|
+
})
|
|
1901
|
+
}), path);
|
|
1902
|
+
if (!result.ok) throw new Error(result.code ?? result.error ?? copy.providerLifecycleError);
|
|
1903
|
+
if (result.restart_required === true && result.result?.state === "RESTART_PENDING") {
|
|
1904
|
+
const restartPath = "http://127.0.0.1:3080/_dsh/dsh-crew/supervisor/restart";
|
|
1905
|
+
const restarted = await readJson(await fetch(restartPath, {
|
|
1906
|
+
method: "POST",
|
|
1907
|
+
headers: { "content-type": "application/json" },
|
|
1908
|
+
body: JSON.stringify({ confirm: true })
|
|
1909
|
+
}), restartPath);
|
|
1910
|
+
if (!restarted.ok) throw new Error(restarted.code ?? restarted.error ?? copy.providerLifecycleError);
|
|
1911
|
+
const verified = await post(`/providers/${encoded}/verify-delete`, {
|
|
1912
|
+
transaction_id: planned.plan.plan_id,
|
|
1913
|
+
confirm: true
|
|
1914
|
+
});
|
|
1915
|
+
if (!verified.ok || verified.state !== "VERIFIED") throw new Error(verified.code ?? verified.error ?? copy.providerLifecycleError);
|
|
1916
|
+
}
|
|
1917
|
+
setNotice(copy.saved);
|
|
1918
|
+
await refreshProviderInventory();
|
|
1919
|
+
} catch (error) {
|
|
1920
|
+
setNotice(String(error?.message ?? error));
|
|
1921
|
+
} finally {
|
|
1922
|
+
setProviderLifecycleBusy(null);
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1751
1925
|
/** Card: title + one-line description header, then its fields. */
|
|
1752
1926
|
const block = (text, fields, gridded = true) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1753
1927
|
style: S.block,
|
|
@@ -1818,21 +1992,29 @@ function WorkersPanel({ ctx }) {
|
|
|
1818
1992
|
const adaptive = normalizeAdaptive(config?.worker?.model_policy?.adaptive);
|
|
1819
1993
|
const setAdaptiveLocal = (candidate) => {
|
|
1820
1994
|
const next = normalizeAdaptive(candidate);
|
|
1995
|
+
const ordering = next.enabled ? "health-aware" : "manual";
|
|
1821
1996
|
setConfig((current) => ({
|
|
1822
1997
|
...current,
|
|
1823
1998
|
worker: {
|
|
1824
1999
|
...current.worker,
|
|
1825
2000
|
model_policy: {
|
|
1826
2001
|
...current.worker?.model_policy,
|
|
1827
|
-
adaptive: next
|
|
2002
|
+
adaptive: next,
|
|
2003
|
+
ordering
|
|
1828
2004
|
}
|
|
1829
2005
|
}
|
|
1830
2006
|
}));
|
|
1831
|
-
return
|
|
2007
|
+
return {
|
|
2008
|
+
next,
|
|
2009
|
+
ordering
|
|
2010
|
+
};
|
|
1832
2011
|
};
|
|
1833
2012
|
const saveAdaptive = (candidate) => {
|
|
1834
|
-
const next = setAdaptiveLocal(candidate);
|
|
1835
|
-
applyPatch({ worker: { model_policy: {
|
|
2013
|
+
const { next, ordering } = setAdaptiveLocal(candidate);
|
|
2014
|
+
applyPatch({ worker: { model_policy: {
|
|
2015
|
+
adaptive: next,
|
|
2016
|
+
ordering
|
|
2017
|
+
} } });
|
|
1836
2018
|
};
|
|
1837
2019
|
const modelActivity = aggregateModelInvocations(jobs);
|
|
1838
2020
|
const currentSurfaceResponsibilities = surfaceResponsibilities(surface);
|
|
@@ -2131,6 +2313,9 @@ function WorkersPanel({ ctx }) {
|
|
|
2131
2313
|
model: provider?.models?.find((item) => item.id === ref.model)
|
|
2132
2314
|
};
|
|
2133
2315
|
};
|
|
2316
|
+
const modelHealth = (ref) => providerHealth.find((entry) => entry.provider === ref.provider && entry.model === ref.model && entry.fresh === true) ?? null;
|
|
2317
|
+
const healthLabel = (health) => health?.state ? String(health.state).toUpperCase().replace(/-/g, " ") : copy.healthUnprobed;
|
|
2318
|
+
const healthAge = (health) => Number.isFinite(health?.observed_at) ? copy.checkedAgo(Math.max(0, Math.round((Date.now() - health.observed_at) / 1e3))) : "";
|
|
2134
2319
|
const filteredProviders = catalogProviders.map((provider) => ({
|
|
2135
2320
|
...provider,
|
|
2136
2321
|
models: (provider.models ?? []).filter((model) => {
|
|
@@ -2187,6 +2372,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2187
2372
|
},
|
|
2188
2373
|
children: priority.map((ref, index) => {
|
|
2189
2374
|
const meta = modelMeta(ref);
|
|
2375
|
+
const health = modelHealth(ref);
|
|
2190
2376
|
const warning = !meta.provider ? copy.providerUnavailable : !meta.model ? copy.notAdvertised : "";
|
|
2191
2377
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2192
2378
|
style: {
|
|
@@ -2231,6 +2417,15 @@ function WorkersPanel({ ctx }) {
|
|
|
2231
2417
|
ref.model
|
|
2232
2418
|
]
|
|
2233
2419
|
}),
|
|
2420
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2421
|
+
style: {
|
|
2422
|
+
display: "block",
|
|
2423
|
+
fontSize: 10.5,
|
|
2424
|
+
color: health?.state === "callable" ? "#3fb950" : health ? "#c98735" : "inherit",
|
|
2425
|
+
opacity: health ? .9 : .5
|
|
2426
|
+
},
|
|
2427
|
+
children: [healthLabel(health), healthAge(health) ? ` · ${healthAge(health)}` : ""]
|
|
2428
|
+
}),
|
|
2234
2429
|
warning && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2235
2430
|
style: {
|
|
2236
2431
|
display: "block",
|
|
@@ -2536,7 +2731,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2536
2731
|
block({
|
|
2537
2732
|
t: copy.autoReview,
|
|
2538
2733
|
d: copy.autoReviewHint
|
|
2539
|
-
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2734
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2540
2735
|
style: {
|
|
2541
2736
|
...S.field,
|
|
2542
2737
|
flexDirection: "row",
|
|
@@ -2551,7 +2746,65 @@ function WorkersPanel({ ctx }) {
|
|
|
2551
2746
|
style: { fontSize: 12.5 },
|
|
2552
2747
|
children: copy.autoReview
|
|
2553
2748
|
})]
|
|
2554
|
-
}),
|
|
2749
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2750
|
+
style: S.field,
|
|
2751
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2752
|
+
style: S.fieldLabel,
|
|
2753
|
+
children: copy.reviewGate
|
|
2754
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2755
|
+
value: config.review?.gate ?? "required",
|
|
2756
|
+
onChange: (v) => reviewField({ gate: v }),
|
|
2757
|
+
options: [
|
|
2758
|
+
"required",
|
|
2759
|
+
"optional",
|
|
2760
|
+
"off"
|
|
2761
|
+
].map((value) => ({
|
|
2762
|
+
value,
|
|
2763
|
+
label: copy.reviewGateDesc[value]
|
|
2764
|
+
}))
|
|
2765
|
+
})]
|
|
2766
|
+
})] })),
|
|
2767
|
+
block({
|
|
2768
|
+
t: copy.modelOrdering,
|
|
2769
|
+
d: copy.orderingHint
|
|
2770
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: ["worker", "review"].map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2771
|
+
style: { display: "contents" },
|
|
2772
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2773
|
+
style: S.field,
|
|
2774
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2775
|
+
style: S.fieldLabel,
|
|
2776
|
+
children: [
|
|
2777
|
+
role === "worker" ? copy.roleWorker : copy.roleReviewer,
|
|
2778
|
+
" · ",
|
|
2779
|
+
copy.modelOrdering
|
|
2780
|
+
]
|
|
2781
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2782
|
+
value: config?.[role]?.model_policy?.ordering ?? "manual",
|
|
2783
|
+
onChange: (v) => rolePolicyField(role, { ordering: v }),
|
|
2784
|
+
options: ["manual", "health-aware"].map((value) => ({
|
|
2785
|
+
value,
|
|
2786
|
+
label: copy.orderingDesc[value]
|
|
2787
|
+
}))
|
|
2788
|
+
})]
|
|
2789
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2790
|
+
style: S.field,
|
|
2791
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2792
|
+
style: S.fieldLabel,
|
|
2793
|
+
children: [
|
|
2794
|
+
role === "worker" ? copy.roleWorker : copy.roleReviewer,
|
|
2795
|
+
" · ",
|
|
2796
|
+
copy.healthGate
|
|
2797
|
+
]
|
|
2798
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2799
|
+
value: config?.[role]?.model_policy?.health_gate ?? "hard-failures",
|
|
2800
|
+
onChange: (v) => rolePolicyField(role, { health_gate: v }),
|
|
2801
|
+
options: ["hard-failures", "off"].map((value) => ({
|
|
2802
|
+
value,
|
|
2803
|
+
label: copy.healthGateDesc[value]
|
|
2804
|
+
}))
|
|
2805
|
+
})]
|
|
2806
|
+
})]
|
|
2807
|
+
}, role)) })),
|
|
2555
2808
|
block({
|
|
2556
2809
|
t: copy.isolation,
|
|
2557
2810
|
d: copy.isolationHint
|
|
@@ -2948,11 +3201,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2948
3201
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2949
3202
|
value: config.mode,
|
|
2950
3203
|
onChange: (v) => field("mode", v),
|
|
2951
|
-
options: [
|
|
2952
|
-
"auto",
|
|
2953
|
-
"hub",
|
|
2954
|
-
"standalone"
|
|
2955
|
-
].map((m) => ({
|
|
3204
|
+
options: ["auto", "hub"].map((m) => ({
|
|
2956
3205
|
value: m,
|
|
2957
3206
|
label: copy.modeDesc[m]
|
|
2958
3207
|
}))
|
|
@@ -3190,6 +3439,80 @@ function WorkersPanel({ ctx }) {
|
|
|
3190
3439
|
})
|
|
3191
3440
|
] }))
|
|
3192
3441
|
}),
|
|
3442
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
|
|
3443
|
+
sectionId: "harnessProviders",
|
|
3444
|
+
title: copy.sectionNames.harnessProviders,
|
|
3445
|
+
summary: sectionSummary(copy.providerCount(providerInventory?.records?.length ?? 0), providerInventoryError ? copy.providerLifecycleError : ""),
|
|
3446
|
+
expanded: !!expandedSections.harnessProviders,
|
|
3447
|
+
onToggle: () => toggleSection("harnessProviders"),
|
|
3448
|
+
children: [
|
|
3449
|
+
providerInventoryError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3450
|
+
style: {
|
|
3451
|
+
fontSize: 12,
|
|
3452
|
+
color: "#c55"
|
|
3453
|
+
},
|
|
3454
|
+
children: providerInventoryError
|
|
3455
|
+
}),
|
|
3456
|
+
!providerInventoryError && (providerInventory?.records?.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3457
|
+
style: {
|
|
3458
|
+
fontSize: 12,
|
|
3459
|
+
opacity: .55
|
|
3460
|
+
},
|
|
3461
|
+
children: copy.providerNoInventory
|
|
3462
|
+
}),
|
|
3463
|
+
(providerInventory?.records ?? []).map((record) => {
|
|
3464
|
+
const blocked = record.ownership === "harness" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0;
|
|
3465
|
+
const lifecycle = record.desired_state === "absent" ? "absent" : record.lifecycle?.enabled === false ? "disabled" : record.lifecycle?.catalogued ? "catalogued" : "configured";
|
|
3466
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3467
|
+
style: {
|
|
3468
|
+
display: "flex",
|
|
3469
|
+
alignItems: "center",
|
|
3470
|
+
gap: 8,
|
|
3471
|
+
fontSize: 12.5,
|
|
3472
|
+
flexWrap: "wrap"
|
|
3473
|
+
},
|
|
3474
|
+
children: [
|
|
3475
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3476
|
+
style: { fontWeight: 600 },
|
|
3477
|
+
children: record.display_name || record.id
|
|
3478
|
+
}),
|
|
3479
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3480
|
+
style: {
|
|
3481
|
+
...S.chip(record.lifecycle?.enabled === true),
|
|
3482
|
+
...S.mono
|
|
3483
|
+
},
|
|
3484
|
+
children: record.id
|
|
3485
|
+
}),
|
|
3486
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3487
|
+
style: { opacity: .65 },
|
|
3488
|
+
children: copy.providerState(lifecycle)
|
|
3489
|
+
}),
|
|
3490
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3491
|
+
style: { opacity: .65 },
|
|
3492
|
+
children: copy.providerModels(record.models?.length ?? 0)
|
|
3493
|
+
}),
|
|
3494
|
+
Number(record.references?.active_jobs ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3495
|
+
style: { color: "#c98735" },
|
|
3496
|
+
children: copy.providerJobs(record.references.active_jobs)
|
|
3497
|
+
}),
|
|
3498
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
3499
|
+
record.ownership !== "harness" && record.desired_state !== "absent" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3500
|
+
type: "button",
|
|
3501
|
+
style: {
|
|
3502
|
+
...S.btn,
|
|
3503
|
+
padding: "2px 8px"
|
|
3504
|
+
},
|
|
3505
|
+
disabled: blocked || providerLifecycleBusy !== null,
|
|
3506
|
+
onClick: () => {
|
|
3507
|
+
deleteHarnessProvider(record);
|
|
3508
|
+
},
|
|
3509
|
+
children: providerLifecycleBusy === record.id ? copy.working : copy.providerDeletePlan
|
|
3510
|
+
})
|
|
3511
|
+
]
|
|
3512
|
+
}, record.id);
|
|
3513
|
+
})
|
|
3514
|
+
]
|
|
3515
|
+
}),
|
|
3193
3516
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
3194
3517
|
sectionId: "providers",
|
|
3195
3518
|
title: copy.sectionNames.providers,
|
package/package.json
CHANGED