@ran-sh/dsh-crew 0.5.6 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -7
- package/README.zh.md +18 -7
- package/docs/installation.md +1 -1
- package/docs/ui-surfaces.md +65 -46
- package/lib/client.js +565 -28
- package/official-web-bridge/lib/client.js +565 -28
- package/package.json +1 -1
- package/src/client/collapsible-sections.mjs +3 -2
- package/src/client/host-readiness.mjs +16 -3
- package/src/client/index.tsx +331 -54
- package/src/config-readiness.mjs +66 -5
- package/src/credential-lifecycle.mjs +73 -0
- package/src/credential-purge-adapters.mjs +127 -0
- package/src/credential-purge-state.mjs +59 -0
- package/src/credential-reference-inventory.mjs +78 -0
- package/src/dsh-cli-runtime.mjs +36 -9
- package/src/extension-contract.mjs +16 -10
- package/src/hub/index.mjs +1004 -438
- package/src/hub-client.mjs +25 -5
- package/src/hub-compatibility.mjs +23 -7
- package/src/install/install.mjs +4 -4
- package/src/install/npx-lifecycle.mjs +495 -134
- package/src/job-contracts.mjs +27 -6
- 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 +248 -95
- package/src/policy.mjs +62 -31
- package/src/provider-config-scrub.mjs +71 -0
- package/src/provider-delete-adapters.mjs +451 -0
- package/src/provider-desired-state.mjs +37 -0
- package/src/provider-health.mjs +124 -0
- package/src/provider-inventory.mjs +132 -0
- package/src/provider-lifecycle-state.mjs +95 -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 +66 -21
- package/src/runtime-readiness-snapshot.mjs +87 -0
- package/src/server.mjs +64 -21
- 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
|
]);
|
|
@@ -178,7 +179,16 @@ function componentState(status, keys, { aligned = false } = {}) {
|
|
|
178
179
|
if (evidence.some((value) => typeof value !== "boolean")) return READINESS_STATES.UNKNOWN;
|
|
179
180
|
return evidence.every(Boolean) && (!aligned || status.components.target_alignment === true) ? READINESS_STATES.READY : READINESS_STATES.DEGRADED;
|
|
180
181
|
}
|
|
181
|
-
function runtimeState(runtime) {
|
|
182
|
+
function runtimeState(runtime, readinessSnapshot) {
|
|
183
|
+
if (readinessSnapshot && typeof readinessSnapshot === "object") {
|
|
184
|
+
const identity = readinessSnapshot.runtime;
|
|
185
|
+
if (!(identity?.execution_plane === "hub-3210" && identity.profile === "dsh-crew" && Number(identity.listen_port) === 3210 && typeof identity.runtime_id === "string" && identity.runtime_id.trim())) return READINESS_STATES.UNAVAILABLE;
|
|
186
|
+
const row = Array.isArray(readinessSnapshot.readiness_matrix?.rows) ? readinessSnapshot.readiness_matrix.rows.find((entry) => entry?.id === "hub_compatibility") : void 0;
|
|
187
|
+
if (row?.status === "PASS") return READINESS_STATES.READY;
|
|
188
|
+
if (row?.status === "FAIL") return READINESS_STATES.UNAVAILABLE;
|
|
189
|
+
if (row?.status === "NOT_RUN" || row?.status === "SKIP") return READINESS_STATES.DEGRADED;
|
|
190
|
+
return READINESS_STATES.UNKNOWN;
|
|
191
|
+
}
|
|
182
192
|
if (runtime === void 0) return READINESS_STATES.UNKNOWN;
|
|
183
193
|
if (runtime === null) return READINESS_STATES.UNAVAILABLE;
|
|
184
194
|
return runtime?.ok === true && runtime.service === "dsh-crew-hub" ? READINESS_STATES.READY : READINESS_STATES.DEGRADED;
|
|
@@ -189,7 +199,7 @@ function bridgeState(surface) {
|
|
|
189
199
|
return READINESS_STATES.UNKNOWN;
|
|
190
200
|
}
|
|
191
201
|
/** Project only structured, non-secret installer/runtime evidence. */
|
|
192
|
-
function projectHostReadiness({ installStatus, runtime, surface } = {}) {
|
|
202
|
+
function projectHostReadiness({ installStatus, runtime, surface, readinessSnapshot } = {}) {
|
|
193
203
|
const codex = installStatus?.codex;
|
|
194
204
|
const claude = installStatus?.claude;
|
|
195
205
|
const zcode = installStatus?.zcode;
|
|
@@ -229,7 +239,7 @@ function projectHostReadiness({ installStatus, runtime, surface } = {}) {
|
|
|
229
239
|
},
|
|
230
240
|
{
|
|
231
241
|
id: "crew_harness",
|
|
232
|
-
state: runtimeState(runtime),
|
|
242
|
+
state: runtimeState(runtime, readinessSnapshot),
|
|
233
243
|
detail: runtime?.runtime_version ?? null
|
|
234
244
|
},
|
|
235
245
|
{
|
|
@@ -375,7 +385,8 @@ const COPY = {
|
|
|
375
385
|
adaptive: "自适应路由",
|
|
376
386
|
runtime: "运行 / 生效边界",
|
|
377
387
|
multimodal: "视觉与生图",
|
|
378
|
-
|
|
388
|
+
harnessProviders: "Harness Providers",
|
|
389
|
+
providers: "多模态适配器",
|
|
379
390
|
jobs: "任务状态"
|
|
380
391
|
},
|
|
381
392
|
openHarness: "打开 3210 Crew Harness →",
|
|
@@ -432,7 +443,7 @@ const COPY = {
|
|
|
432
443
|
"follow-dsh": "跟随 Harness · 使用各档独立的模型优先级",
|
|
433
444
|
"deepseek-official": "DeepSeek Official · 使用旧版固定模型路由"
|
|
434
445
|
},
|
|
435
|
-
workerProviderHint: "仅作用于 Hub 模式;Standalone
|
|
446
|
+
workerProviderHint: "仅作用于 Hub 模式;Standalone 仅保留为旧版迁移元数据,不是生产执行路径。",
|
|
436
447
|
workerModels: "Worker 模型",
|
|
437
448
|
refreshHarnessModels: "刷新 Harness 模型",
|
|
438
449
|
refreshingModels: "刷新中…",
|
|
@@ -444,6 +455,8 @@ const COPY = {
|
|
|
444
455
|
notAdvertised: "当前未列出",
|
|
445
456
|
noPriority: "未配置优先项",
|
|
446
457
|
modelPoolSummary: (p, m) => `Providers: ${p} · Models: ${m}`,
|
|
458
|
+
healthUnprobed: "UNPROBED",
|
|
459
|
+
checkedAgo: (s) => `${s} 秒前检查`,
|
|
447
460
|
partialCatalog: "部分 Provider 读取失败",
|
|
448
461
|
catalogFailed: "无法读取 Harness 模型目录",
|
|
449
462
|
removePriority: "移除",
|
|
@@ -491,6 +504,31 @@ const COPY = {
|
|
|
491
504
|
visionProvider: "视觉 provider",
|
|
492
505
|
visionModel: "视觉模型",
|
|
493
506
|
imagegenProvider: "生图 provider",
|
|
507
|
+
harnessProviders: "Harness Providers",
|
|
508
|
+
harnessProviderHint: "3210 Crew Harness 的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。",
|
|
509
|
+
providerState: (state) => `状态:${state}`,
|
|
510
|
+
providerModels: (count) => `${count} 个模型`,
|
|
511
|
+
providerJobs: (count) => `${count} 个运行任务`,
|
|
512
|
+
providerDeletePlan: "生成删除计划",
|
|
513
|
+
providerDelete: "确认删除",
|
|
514
|
+
providerPlanReady: "计划已生成;再次确认后才会修改 3210 配置。",
|
|
515
|
+
providerRollback: "回滚",
|
|
516
|
+
providerRollbackConfirm: "按已保存事务恢复这个 Harness Provider 及原有路由?",
|
|
517
|
+
providerRollbackUnavailable: "没有可验证的回滚事务",
|
|
518
|
+
providerReplacement: "替换 Harness 默认 Provider(输入 Provider id)",
|
|
519
|
+
providerDeleteConfirm: "确认按计划删除这个 Harness Provider?会清理路由引用并重启 3210。",
|
|
520
|
+
providerDeleteBlocked: "当前 Provider 不能删除(内置、在用、已删除或缺少可用替换)。",
|
|
521
|
+
providerLifecycleError: "Provider 生命周期操作失败",
|
|
522
|
+
providerNoInventory: "暂时无法读取 3210 Provider inventory。",
|
|
523
|
+
credentialReferences: "Credential 引用",
|
|
524
|
+
credentialReferenceHint: "仅显示引用名、归属和孤儿状态;不会读取或删除密钥。",
|
|
525
|
+
credentialOrphan: "孤儿 · 可单独申请清理",
|
|
526
|
+
credentialInUse: (count) => `使用中 · ${count} 个 Provider`,
|
|
527
|
+
credentialPurgePlan: "申请清理",
|
|
528
|
+
credentialPurgeConfirm: "这是不可恢复的 Crew-owned 凭据清理,确认继续?",
|
|
529
|
+
credentialPurging: "清理中…",
|
|
530
|
+
credentialPurgeUnavailable: "凭据清理不可用",
|
|
531
|
+
credentialPurgeUnverified: "已执行清理,但验证失败;引用仍保留用于恢复检查",
|
|
494
532
|
customProviders: "自定义 Provider",
|
|
495
533
|
addProvider: "+ 添加 Provider",
|
|
496
534
|
noCustomProviders: "暂无。添加后会出现在上方的视觉 / 生图 provider 选择里。",
|
|
@@ -544,6 +582,23 @@ const COPY = {
|
|
|
544
582
|
roleStateHint: "auto:orchestrator 可自动派发;manual:仅显式点名;disabled:禁用。未显式设置时由协作模式推导。",
|
|
545
583
|
autoReview: "自动复审",
|
|
546
584
|
autoReviewHint: "worker 成功后自动运行一次 reviewer(独立模型策略,只读)",
|
|
585
|
+
modelOrdering: "候选排序",
|
|
586
|
+
orderingDesc: {
|
|
587
|
+
manual: "手动顺序",
|
|
588
|
+
"health-aware": "健康感知"
|
|
589
|
+
},
|
|
590
|
+
orderingHint: "手动 priority 始终保持顺序;健康感知仅重排未显式锁定的自动候选。",
|
|
591
|
+
healthGate: "健康门",
|
|
592
|
+
healthGateDesc: {
|
|
593
|
+
"hard-failures": "跳过明确不可调用",
|
|
594
|
+
off: "关闭健康门"
|
|
595
|
+
},
|
|
596
|
+
reviewGate: "Reviewer 验收门",
|
|
597
|
+
reviewGateDesc: {
|
|
598
|
+
required: "required · 未批准则失败",
|
|
599
|
+
optional: "optional · 记录但不阻断",
|
|
600
|
+
off: "off · 不自动复审"
|
|
601
|
+
},
|
|
547
602
|
isolation: "工作区隔离",
|
|
548
603
|
isolationHint: "worktree 为默认;非 Git 仓库时需显式 shared,否则任务以 NOT_GIT_REPOSITORY 失败。",
|
|
549
604
|
isolationDesc: {
|
|
@@ -570,9 +625,8 @@ const COPY = {
|
|
|
570
625
|
d: "接入自己的 API 或本地命令;保存后会出现在上面的视觉 / 生图 provider 选择里。"
|
|
571
626
|
},
|
|
572
627
|
modeDesc: {
|
|
573
|
-
auto: "auto · 优先
|
|
574
|
-
hub: "hub · 必须
|
|
575
|
-
standalone: "standalone · 独立进程"
|
|
628
|
+
auto: "auto · 优先 3210",
|
|
629
|
+
hub: "hub · 必须 3210"
|
|
576
630
|
},
|
|
577
631
|
save: "保存",
|
|
578
632
|
saved: "已保存",
|
|
@@ -654,7 +708,8 @@ const COPY = {
|
|
|
654
708
|
adaptive: "Adaptive routing",
|
|
655
709
|
runtime: "Runtime / activation boundaries",
|
|
656
710
|
multimodal: "Vision & image generation",
|
|
657
|
-
|
|
711
|
+
harnessProviders: "Harness Providers",
|
|
712
|
+
providers: "Multimodal adapters",
|
|
658
713
|
jobs: "Task status"
|
|
659
714
|
},
|
|
660
715
|
openHarness: "Open 3210 Crew Harness →",
|
|
@@ -711,7 +766,7 @@ const COPY = {
|
|
|
711
766
|
"follow-dsh": "Follow Harness · use each tier's ordered model priority",
|
|
712
767
|
"deepseek-official": "DeepSeek Official · use legacy fixed model routing"
|
|
713
768
|
},
|
|
714
|
-
workerProviderHint: "Applies to Hub workers only; Standalone
|
|
769
|
+
workerProviderHint: "Applies to Hub workers only; Standalone is legacy migration metadata only and is not a production execution path.",
|
|
715
770
|
workerModels: "Worker Models",
|
|
716
771
|
refreshHarnessModels: "Refresh Harness Models",
|
|
717
772
|
refreshingModels: "Refreshing…",
|
|
@@ -723,6 +778,8 @@ const COPY = {
|
|
|
723
778
|
notAdvertised: "Currently not advertised",
|
|
724
779
|
noPriority: "No priority models configured",
|
|
725
780
|
modelPoolSummary: (p, m) => `Providers: ${p} · Models: ${m}`,
|
|
781
|
+
healthUnprobed: "UNPROBED",
|
|
782
|
+
checkedAgo: (s) => `checked ${s}s ago`,
|
|
726
783
|
partialCatalog: "Some providers failed to load",
|
|
727
784
|
catalogFailed: "Unable to read Harness model catalog",
|
|
728
785
|
removePriority: "Remove",
|
|
@@ -770,6 +827,31 @@ const COPY = {
|
|
|
770
827
|
visionProvider: "Vision provider",
|
|
771
828
|
visionModel: "Vision model",
|
|
772
829
|
imagegenProvider: "Image-gen provider",
|
|
830
|
+
harnessProviders: "Harness Providers",
|
|
831
|
+
harnessProviderHint: "Live Provider registration and lifecycle in the 3210 Crew Harness; separate from vision / image-gen adapters.",
|
|
832
|
+
providerState: (state) => `State: ${state}`,
|
|
833
|
+
providerModels: (count) => `${count} models`,
|
|
834
|
+
providerJobs: (count) => `${count} running jobs`,
|
|
835
|
+
providerDeletePlan: "Plan deletion",
|
|
836
|
+
providerDelete: "Confirm delete",
|
|
837
|
+
providerPlanReady: "Plan ready; a second confirmation is required before changing 3210 config.",
|
|
838
|
+
providerRollback: "Rollback",
|
|
839
|
+
providerRollbackConfirm: "Restore this Harness Provider and its previous routing from the saved transaction?",
|
|
840
|
+
providerRollbackUnavailable: "No verified rollback transaction is available",
|
|
841
|
+
providerReplacement: "Replacement Harness Default provider id",
|
|
842
|
+
providerDeleteConfirm: "Delete this Harness Provider per the plan? Routing refs will be scrubbed and 3210 restarted.",
|
|
843
|
+
providerDeleteBlocked: "This Provider cannot be deleted (built-in, in use, already absent, or missing a valid replacement).",
|
|
844
|
+
providerLifecycleError: "Provider lifecycle operation failed",
|
|
845
|
+
providerNoInventory: "3210 Provider inventory is temporarily unavailable.",
|
|
846
|
+
credentialReferences: "Credential references",
|
|
847
|
+
credentialReferenceHint: "Names, ownership, and orphan status only; values are never read or deleted.",
|
|
848
|
+
credentialOrphan: "orphan · separate purge approval required",
|
|
849
|
+
credentialInUse: (count) => `in use · ${count} provider(s)`,
|
|
850
|
+
credentialPurgePlan: "Purge…",
|
|
851
|
+
credentialPurgeConfirm: "This irreversibly removes a Crew-owned credential. Continue?",
|
|
852
|
+
credentialPurging: "Purging…",
|
|
853
|
+
credentialPurgeUnavailable: "Credential purge unavailable",
|
|
854
|
+
credentialPurgeUnverified: "Purge ran but verification failed; the reference remains visible for recovery",
|
|
773
855
|
customProviders: "Custom providers",
|
|
774
856
|
addProvider: "+ Add provider",
|
|
775
857
|
noCustomProviders: "None yet. Added providers appear in the vision / image-gen selects above.",
|
|
@@ -823,6 +905,23 @@ const COPY = {
|
|
|
823
905
|
roleStateHint: "auto = the orchestrator may dispatch automatically; manual = only when explicitly named; disabled = off. When unset, derived from the collaboration mode.",
|
|
824
906
|
autoReview: "Automatic review",
|
|
825
907
|
autoReviewHint: "Run one reviewer pass (independent model policy, read-only) after a successful worker run",
|
|
908
|
+
modelOrdering: "Candidate ordering",
|
|
909
|
+
orderingDesc: {
|
|
910
|
+
manual: "Manual order",
|
|
911
|
+
"health-aware": "Health-aware"
|
|
912
|
+
},
|
|
913
|
+
orderingHint: "Explicit priority always keeps its order; health-aware mode only reorders automatic, unlocked candidates.",
|
|
914
|
+
healthGate: "Health gate",
|
|
915
|
+
healthGateDesc: {
|
|
916
|
+
"hard-failures": "Skip known hard failures",
|
|
917
|
+
off: "Health gate off"
|
|
918
|
+
},
|
|
919
|
+
reviewGate: "Reviewer acceptance gate",
|
|
920
|
+
reviewGateDesc: {
|
|
921
|
+
required: "required · fail unless approved",
|
|
922
|
+
optional: "optional · record without blocking",
|
|
923
|
+
off: "off · no automatic review"
|
|
924
|
+
},
|
|
826
925
|
isolation: "Workspace isolation",
|
|
827
926
|
isolationHint: "worktree is the default; non-git workspaces require explicit shared, otherwise jobs fail with NOT_GIT_REPOSITORY.",
|
|
828
927
|
isolationDesc: {
|
|
@@ -849,9 +948,8 @@ const COPY = {
|
|
|
849
948
|
d: "Bring your own API or local command; saved providers appear in the vision / image-gen selects above."
|
|
850
949
|
},
|
|
851
950
|
modeDesc: {
|
|
852
|
-
auto: "auto · prefer
|
|
853
|
-
hub: "hub · require
|
|
854
|
-
standalone: "standalone"
|
|
951
|
+
auto: "auto · prefer 3210",
|
|
952
|
+
hub: "hub · require 3210"
|
|
855
953
|
},
|
|
856
954
|
save: "Save",
|
|
857
955
|
saved: "Saved",
|
|
@@ -1339,12 +1437,20 @@ function WorkersPanel({ ctx }) {
|
|
|
1339
1437
|
const [modelCatalog, setModelCatalog] = (0, react.useState)(null);
|
|
1340
1438
|
const [modelCatalogError, setModelCatalogError] = (0, react.useState)("");
|
|
1341
1439
|
const [modelCatalogBusy, setModelCatalogBusy] = (0, react.useState)(false);
|
|
1440
|
+
const [providerInventory, setProviderInventory] = (0, react.useState)(null);
|
|
1441
|
+
const [providerHealth, setProviderHealth] = (0, react.useState)([]);
|
|
1442
|
+
const [credentialRefs, setCredentialRefs] = (0, react.useState)([]);
|
|
1443
|
+
const [credentialUnverified, setCredentialUnverified] = (0, react.useState)([]);
|
|
1444
|
+
const [credentialLifecycleBusy, setCredentialLifecycleBusy] = (0, react.useState)(null);
|
|
1445
|
+
const [providerInventoryError, setProviderInventoryError] = (0, react.useState)("");
|
|
1446
|
+
const [providerLifecycleBusy, setProviderLifecycleBusy] = (0, react.useState)(null);
|
|
1342
1447
|
const [modelPickerTier, setModelPickerTier] = (0, react.useState)(null);
|
|
1343
1448
|
const [modelQuery, setModelQuery] = (0, react.useState)("");
|
|
1344
1449
|
const [testResult, setTestResult] = (0, react.useState)(null);
|
|
1345
1450
|
const [expandedSections, setExpandedSections] = (0, react.useState)(() => readSectionState(sectionStorage()));
|
|
1346
1451
|
const [surface, setSurface] = (0, react.useState)("detecting");
|
|
1347
1452
|
const [runtimeInfo, setRuntimeInfo] = (0, react.useState)(void 0);
|
|
1453
|
+
const [readinessSnapshot, setReadinessSnapshot] = (0, react.useState)(void 0);
|
|
1348
1454
|
const toggleSection = (sectionId) => {
|
|
1349
1455
|
setExpandedSections((current) => ({
|
|
1350
1456
|
...current,
|
|
@@ -1362,6 +1468,9 @@ function WorkersPanel({ ctx }) {
|
|
|
1362
1468
|
"pro"
|
|
1363
1469
|
]));
|
|
1364
1470
|
}, [modelCatalogError]);
|
|
1471
|
+
(0, react.useEffect)(() => {
|
|
1472
|
+
if (providerInventoryError) setExpandedSections((current) => openSections(current, ["harnessProviders"]));
|
|
1473
|
+
}, [providerInventoryError]);
|
|
1365
1474
|
(0, react.useEffect)(() => {
|
|
1366
1475
|
if (testResult && !testResult.busy && testResult.ok === false) setExpandedSections((current) => openSections(current, ["providers"]));
|
|
1367
1476
|
}, [testResult]);
|
|
@@ -1422,11 +1531,15 @@ function WorkersPanel({ ctx }) {
|
|
|
1422
1531
|
}, [detectSurface]);
|
|
1423
1532
|
const refreshAll = (0, react.useCallback)(async () => {
|
|
1424
1533
|
try {
|
|
1425
|
-
const [j, s, c, pr] = await Promise.all([
|
|
1534
|
+
const [j, s, c, pr, pi, ph, cr, ext] = await Promise.all([
|
|
1426
1535
|
get("/jobs"),
|
|
1427
1536
|
get("/install/status"),
|
|
1428
1537
|
get("/config"),
|
|
1429
|
-
get("/presets")
|
|
1538
|
+
get("/presets"),
|
|
1539
|
+
get("/providers").catch(() => null),
|
|
1540
|
+
get("/provider-health").catch(() => null),
|
|
1541
|
+
get("/credential-references").catch(() => null),
|
|
1542
|
+
get("/extension").catch(() => null)
|
|
1430
1543
|
]);
|
|
1431
1544
|
if (j.ok) setJobs(j.jobs ?? []);
|
|
1432
1545
|
if (s.ok) setStatus(s.status);
|
|
@@ -1438,14 +1551,22 @@ function WorkersPanel({ ctx }) {
|
|
|
1438
1551
|
value: x.id,
|
|
1439
1552
|
label: x.name ?? x.id
|
|
1440
1553
|
}))]);
|
|
1554
|
+
if (pi?.ok) setProviderInventory(pi);
|
|
1555
|
+
if (ph?.ok) setProviderHealth(ph.health ?? []);
|
|
1556
|
+
if (cr?.ok) {
|
|
1557
|
+
setCredentialRefs(cr.records ?? []);
|
|
1558
|
+
setCredentialUnverified(cr.unverified_purges ?? []);
|
|
1559
|
+
}
|
|
1560
|
+
if (ext?.ok) setReadinessSnapshot(ext.extension?.readiness_snapshot ?? void 0);
|
|
1441
1561
|
} catch {}
|
|
1442
1562
|
}, [get]);
|
|
1443
1563
|
(0, react.useEffect)(() => {
|
|
1444
1564
|
if (surface !== CREW_UI_SURFACES.OFFICIAL) return void 0;
|
|
1445
1565
|
refreshAll();
|
|
1446
1566
|
const timer = setInterval(() => {
|
|
1447
|
-
get("/jobs").then((j) => {
|
|
1567
|
+
Promise.all([get("/jobs"), get("/provider-health").catch(() => null)]).then(([j, health]) => {
|
|
1448
1568
|
if (j.ok) setJobs(j.jobs ?? []);
|
|
1569
|
+
if (health?.ok) setProviderHealth(health.health ?? []);
|
|
1449
1570
|
}).catch(() => {});
|
|
1450
1571
|
}, 3e3);
|
|
1451
1572
|
return () => clearInterval(timer);
|
|
@@ -1467,9 +1588,68 @@ function WorkersPanel({ ctx }) {
|
|
|
1467
1588
|
setModelCatalogBusy(false);
|
|
1468
1589
|
}
|
|
1469
1590
|
}, [get, copy]);
|
|
1591
|
+
const refreshProviderInventory = (0, react.useCallback)(async () => {
|
|
1592
|
+
setProviderInventoryError("");
|
|
1593
|
+
try {
|
|
1594
|
+
const [result, health, refs] = await Promise.all([
|
|
1595
|
+
get("/providers"),
|
|
1596
|
+
get("/provider-health").catch(() => null),
|
|
1597
|
+
get("/credential-references").catch(() => null)
|
|
1598
|
+
]);
|
|
1599
|
+
if (!result.ok) throw new Error(result.error ?? copy.providerNoInventory);
|
|
1600
|
+
setProviderInventory(result);
|
|
1601
|
+
if (health?.ok) setProviderHealth(health.health ?? []);
|
|
1602
|
+
if (refs?.ok) {
|
|
1603
|
+
setCredentialRefs(refs.records ?? []);
|
|
1604
|
+
setCredentialUnverified(refs.unverified_purges ?? []);
|
|
1605
|
+
}
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
setProviderInventoryError(error?.message ?? copy.providerNoInventory);
|
|
1608
|
+
}
|
|
1609
|
+
}, [get, copy]);
|
|
1610
|
+
const purgeCredentialRef = (0, react.useCallback)(async (ref) => {
|
|
1611
|
+
if (ref?.ownership !== "crew-owned" || ref?.orphan !== true || ref?.purge_capability !== "eligible") return;
|
|
1612
|
+
const referenceId = String(ref.reference_id ?? "");
|
|
1613
|
+
if (!referenceId || !window.confirm(copy.credentialPurgeConfirm)) return;
|
|
1614
|
+
setCredentialLifecycleBusy(referenceId);
|
|
1615
|
+
setNotice(copy.working);
|
|
1616
|
+
try {
|
|
1617
|
+
const encoded = encodeURIComponent(referenceId);
|
|
1618
|
+
const planned = await post(`/credential-references/${encoded}/purge-plan`, {});
|
|
1619
|
+
if (!planned.ok || !planned.plan) throw new Error(planned.code ?? copy.credentialPurgeUnavailable);
|
|
1620
|
+
if (!window.confirm(`${copy.credentialPurgeConfirm}\n\n${referenceId}`)) return;
|
|
1621
|
+
const result = await readJson(await fetch(withLang(`/credential-references/${encoded}`), {
|
|
1622
|
+
method: "DELETE",
|
|
1623
|
+
headers: { "content-type": "application/json" },
|
|
1624
|
+
body: JSON.stringify({
|
|
1625
|
+
plan_id: planned.plan.plan_id,
|
|
1626
|
+
expected_revision: planned.plan.expected_revision,
|
|
1627
|
+
confirm: true,
|
|
1628
|
+
lang: locale === "zh" ? "zh" : "en"
|
|
1629
|
+
})
|
|
1630
|
+
}), `/credential-references/${encoded}`);
|
|
1631
|
+
if (!result.ok || result.state !== "VERIFIED") throw new Error(result.code ?? copy.credentialPurgeUnavailable);
|
|
1632
|
+
setNotice(copy.saved);
|
|
1633
|
+
await refreshProviderInventory();
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
setNotice(String(error?.message ?? error));
|
|
1636
|
+
} finally {
|
|
1637
|
+
setCredentialLifecycleBusy(null);
|
|
1638
|
+
}
|
|
1639
|
+
}, [
|
|
1640
|
+
copy,
|
|
1641
|
+
locale,
|
|
1642
|
+
post,
|
|
1643
|
+
readJson,
|
|
1644
|
+
refreshProviderInventory,
|
|
1645
|
+
withLang
|
|
1646
|
+
]);
|
|
1470
1647
|
(0, react.useEffect)(() => {
|
|
1471
1648
|
if (surface === CREW_UI_SURFACES.OFFICIAL) refreshHarnessModels();
|
|
1472
1649
|
}, [surface, refreshHarnessModels]);
|
|
1650
|
+
(0, react.useEffect)(() => {
|
|
1651
|
+
if (surface === CREW_UI_SURFACES.OFFICIAL) refreshProviderInventory();
|
|
1652
|
+
}, [surface, refreshProviderInventory]);
|
|
1473
1653
|
const act = (0, react.useCallback)(async (target, confirmName) => {
|
|
1474
1654
|
if (confirmName && !window.confirm(copy.confirmRestore(confirmName))) return;
|
|
1475
1655
|
setBusy(true);
|
|
@@ -1524,6 +1704,36 @@ function WorkersPanel({ ctx }) {
|
|
|
1524
1704
|
...c,
|
|
1525
1705
|
[key]: value
|
|
1526
1706
|
}));
|
|
1707
|
+
const rolePolicyField = (role, patch) => {
|
|
1708
|
+
const effectivePatch = role === "worker" && (patch.ordering === "manual" || patch.ordering === "health-aware") ? {
|
|
1709
|
+
...patch,
|
|
1710
|
+
adaptive: {
|
|
1711
|
+
...normalizeAdaptive(config?.worker?.model_policy?.adaptive),
|
|
1712
|
+
enabled: patch.ordering === "health-aware"
|
|
1713
|
+
}
|
|
1714
|
+
} : patch;
|
|
1715
|
+
setConfig((current) => ({
|
|
1716
|
+
...current,
|
|
1717
|
+
[role]: {
|
|
1718
|
+
...current?.[role],
|
|
1719
|
+
model_policy: {
|
|
1720
|
+
...current?.[role]?.model_policy,
|
|
1721
|
+
...effectivePatch
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}));
|
|
1725
|
+
applyPatch({ [role]: { model_policy: effectivePatch } });
|
|
1726
|
+
};
|
|
1727
|
+
const reviewField = (patch) => {
|
|
1728
|
+
setConfig((current) => ({
|
|
1729
|
+
...current,
|
|
1730
|
+
review: {
|
|
1731
|
+
...current?.review,
|
|
1732
|
+
...patch
|
|
1733
|
+
}
|
|
1734
|
+
}));
|
|
1735
|
+
applyPatch({ review: patch });
|
|
1736
|
+
};
|
|
1527
1737
|
const customProviders = config?.custom_providers ?? [];
|
|
1528
1738
|
const extraModels = config?.extra_models ?? {};
|
|
1529
1739
|
const RESERVED_IDS = [
|
|
@@ -1748,6 +1958,96 @@ function WorkersPanel({ ctx }) {
|
|
|
1748
1958
|
}));
|
|
1749
1959
|
applyPatch(patch);
|
|
1750
1960
|
};
|
|
1961
|
+
const deleteHarnessProvider = async (record) => {
|
|
1962
|
+
if (!record || record.ownership === "harness" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0) {
|
|
1963
|
+
setNotice(copy.providerDeleteBlocked);
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
let replacementDefault;
|
|
1967
|
+
if (record.references?.harness_default === true) {
|
|
1968
|
+
const candidate = (providerInventory?.records ?? []).find((item) => item.id !== record.id && item.desired_state !== "absent" && item.ownership !== "harness" && (item.models?.length ?? 0) > 0)?.id ?? "";
|
|
1969
|
+
const entered = window.prompt(copy.providerReplacement, candidate);
|
|
1970
|
+
if (!entered?.trim()) return;
|
|
1971
|
+
replacementDefault = entered.trim();
|
|
1972
|
+
}
|
|
1973
|
+
setProviderLifecycleBusy(record.id);
|
|
1974
|
+
setNotice(copy.working);
|
|
1975
|
+
try {
|
|
1976
|
+
const encoded = encodeURIComponent(record.id);
|
|
1977
|
+
const planned = await post(`/providers/${encoded}/delete-plan`, replacementDefault ? { replacement_default: replacementDefault } : {});
|
|
1978
|
+
if (!planned.ok || !planned.plan) throw new Error(planned.code ?? copy.providerLifecycleError);
|
|
1979
|
+
if (!window.confirm(`${copy.providerPlanReady}\n\n${copy.providerDeleteConfirm}\n${(planned.plan.will_remove ?? []).join("\n")}`)) return;
|
|
1980
|
+
const path = `/providers/${encoded}`;
|
|
1981
|
+
const result = await readJson(await fetch(withLang(path), {
|
|
1982
|
+
method: "DELETE",
|
|
1983
|
+
headers: { "content-type": "application/json" },
|
|
1984
|
+
body: JSON.stringify({
|
|
1985
|
+
plan_id: planned.plan.plan_id,
|
|
1986
|
+
expected_revision: planned.plan.expected_revision,
|
|
1987
|
+
confirm: true,
|
|
1988
|
+
lang: locale === "zh" ? "zh" : "en"
|
|
1989
|
+
})
|
|
1990
|
+
}), path);
|
|
1991
|
+
if (!result.ok) throw new Error(result.code ?? result.error ?? copy.providerLifecycleError);
|
|
1992
|
+
if (result.restart_required === true && result.result?.state === "RESTART_PENDING") {
|
|
1993
|
+
const restartPath = "http://127.0.0.1:3080/_dsh/dsh-crew/supervisor/restart";
|
|
1994
|
+
const restarted = await readJson(await fetch(restartPath, {
|
|
1995
|
+
method: "POST",
|
|
1996
|
+
headers: { "content-type": "application/json" },
|
|
1997
|
+
body: JSON.stringify({ confirm: true })
|
|
1998
|
+
}), restartPath);
|
|
1999
|
+
if (!restarted.ok) throw new Error(restarted.code ?? restarted.error ?? copy.providerLifecycleError);
|
|
2000
|
+
const verified = await post(`/providers/${encoded}/verify-delete`, {
|
|
2001
|
+
transaction_id: planned.plan.plan_id,
|
|
2002
|
+
confirm: true
|
|
2003
|
+
});
|
|
2004
|
+
if (!verified.ok || verified.state !== "VERIFIED") throw new Error(verified.code ?? verified.error ?? copy.providerLifecycleError);
|
|
2005
|
+
}
|
|
2006
|
+
setNotice(copy.saved);
|
|
2007
|
+
await refreshProviderInventory();
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
setNotice(String(error?.message ?? error));
|
|
2010
|
+
} finally {
|
|
2011
|
+
setProviderLifecycleBusy(null);
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
const rollbackHarnessProvider = async (record, transactionId) => {
|
|
2015
|
+
if (!record || !transactionId || record.desired_state !== "absent") {
|
|
2016
|
+
setNotice(copy.providerRollbackUnavailable);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (!window.confirm(copy.providerRollbackConfirm)) return;
|
|
2020
|
+
setProviderLifecycleBusy(`rollback:${record.id}`);
|
|
2021
|
+
setNotice(copy.working);
|
|
2022
|
+
try {
|
|
2023
|
+
const encoded = encodeURIComponent(record.id);
|
|
2024
|
+
const result = await post(`/providers/${encoded}/rollback`, {
|
|
2025
|
+
transaction_id: transactionId,
|
|
2026
|
+
confirm: true
|
|
2027
|
+
});
|
|
2028
|
+
if (!result.ok) throw new Error(result.code ?? result.error ?? copy.providerLifecycleError);
|
|
2029
|
+
if (result.restart_required === true && result.state === "ROLLBACK_PENDING") {
|
|
2030
|
+
const restartPath = "http://127.0.0.1:3080/_dsh/dsh-crew/supervisor/restart";
|
|
2031
|
+
const restarted = await readJson(await fetch(restartPath, {
|
|
2032
|
+
method: "POST",
|
|
2033
|
+
headers: { "content-type": "application/json" },
|
|
2034
|
+
body: JSON.stringify({ confirm: true })
|
|
2035
|
+
}), restartPath);
|
|
2036
|
+
if (!restarted.ok) throw new Error(restarted.code ?? restarted.error ?? copy.providerLifecycleError);
|
|
2037
|
+
const verified = await post(`/providers/${encoded}/verify-rollback`, {
|
|
2038
|
+
transaction_id: transactionId,
|
|
2039
|
+
confirm: true
|
|
2040
|
+
});
|
|
2041
|
+
if (!verified.ok || verified.state !== "ROLLED_BACK") throw new Error(verified.code ?? verified.error ?? copy.providerLifecycleError);
|
|
2042
|
+
} else if (result.state !== "ROLLED_BACK") throw new Error(result.code ?? copy.providerLifecycleError);
|
|
2043
|
+
setNotice(copy.saved);
|
|
2044
|
+
await refreshProviderInventory();
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
setNotice(String(error?.message ?? error));
|
|
2047
|
+
} finally {
|
|
2048
|
+
setProviderLifecycleBusy(null);
|
|
2049
|
+
}
|
|
2050
|
+
};
|
|
1751
2051
|
/** Card: title + one-line description header, then its fields. */
|
|
1752
2052
|
const block = (text, fields, gridded = true) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1753
2053
|
style: S.block,
|
|
@@ -1818,28 +2118,37 @@ function WorkersPanel({ ctx }) {
|
|
|
1818
2118
|
const adaptive = normalizeAdaptive(config?.worker?.model_policy?.adaptive);
|
|
1819
2119
|
const setAdaptiveLocal = (candidate) => {
|
|
1820
2120
|
const next = normalizeAdaptive(candidate);
|
|
2121
|
+
const ordering = next.enabled ? "health-aware" : "manual";
|
|
1821
2122
|
setConfig((current) => ({
|
|
1822
2123
|
...current,
|
|
1823
2124
|
worker: {
|
|
1824
2125
|
...current.worker,
|
|
1825
2126
|
model_policy: {
|
|
1826
2127
|
...current.worker?.model_policy,
|
|
1827
|
-
adaptive: next
|
|
2128
|
+
adaptive: next,
|
|
2129
|
+
ordering
|
|
1828
2130
|
}
|
|
1829
2131
|
}
|
|
1830
2132
|
}));
|
|
1831
|
-
return
|
|
2133
|
+
return {
|
|
2134
|
+
next,
|
|
2135
|
+
ordering
|
|
2136
|
+
};
|
|
1832
2137
|
};
|
|
1833
2138
|
const saveAdaptive = (candidate) => {
|
|
1834
|
-
const next = setAdaptiveLocal(candidate);
|
|
1835
|
-
applyPatch({ worker: { model_policy: {
|
|
2139
|
+
const { next, ordering } = setAdaptiveLocal(candidate);
|
|
2140
|
+
applyPatch({ worker: { model_policy: {
|
|
2141
|
+
adaptive: next,
|
|
2142
|
+
ordering
|
|
2143
|
+
} } });
|
|
1836
2144
|
};
|
|
1837
2145
|
const modelActivity = aggregateModelInvocations(jobs);
|
|
1838
2146
|
const currentSurfaceResponsibilities = surfaceResponsibilities(surface);
|
|
1839
2147
|
const hostReadiness = projectHostReadiness({
|
|
1840
2148
|
installStatus: status,
|
|
1841
2149
|
runtime: runtimeInfo,
|
|
1842
|
-
surface
|
|
2150
|
+
surface,
|
|
2151
|
+
readinessSnapshot
|
|
1843
2152
|
});
|
|
1844
2153
|
if (!currentSurfaceResponsibilities.fullControlPlane) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MinimalCrewPanel, {
|
|
1845
2154
|
locale,
|
|
@@ -2131,6 +2440,9 @@ function WorkersPanel({ ctx }) {
|
|
|
2131
2440
|
model: provider?.models?.find((item) => item.id === ref.model)
|
|
2132
2441
|
};
|
|
2133
2442
|
};
|
|
2443
|
+
const modelHealth = (ref) => providerHealth.find((entry) => entry.provider === ref.provider && entry.model === ref.model && entry.fresh === true) ?? null;
|
|
2444
|
+
const healthLabel = (health) => health?.state ? String(health.state).toUpperCase().replace(/-/g, " ") : copy.healthUnprobed;
|
|
2445
|
+
const healthAge = (health) => Number.isFinite(health?.observed_at) ? copy.checkedAgo(Math.max(0, Math.round((Date.now() - health.observed_at) / 1e3))) : "";
|
|
2134
2446
|
const filteredProviders = catalogProviders.map((provider) => ({
|
|
2135
2447
|
...provider,
|
|
2136
2448
|
models: (provider.models ?? []).filter((model) => {
|
|
@@ -2187,6 +2499,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2187
2499
|
},
|
|
2188
2500
|
children: priority.map((ref, index) => {
|
|
2189
2501
|
const meta = modelMeta(ref);
|
|
2502
|
+
const health = modelHealth(ref);
|
|
2190
2503
|
const warning = !meta.provider ? copy.providerUnavailable : !meta.model ? copy.notAdvertised : "";
|
|
2191
2504
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2192
2505
|
style: {
|
|
@@ -2231,6 +2544,15 @@ function WorkersPanel({ ctx }) {
|
|
|
2231
2544
|
ref.model
|
|
2232
2545
|
]
|
|
2233
2546
|
}),
|
|
2547
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2548
|
+
style: {
|
|
2549
|
+
display: "block",
|
|
2550
|
+
fontSize: 10.5,
|
|
2551
|
+
color: health?.state === "callable" ? "#3fb950" : health ? "#c98735" : "inherit",
|
|
2552
|
+
opacity: health ? .9 : .5
|
|
2553
|
+
},
|
|
2554
|
+
children: [healthLabel(health), healthAge(health) ? ` · ${healthAge(health)}` : ""]
|
|
2555
|
+
}),
|
|
2234
2556
|
warning && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2235
2557
|
style: {
|
|
2236
2558
|
display: "block",
|
|
@@ -2536,7 +2858,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2536
2858
|
block({
|
|
2537
2859
|
t: copy.autoReview,
|
|
2538
2860
|
d: copy.autoReviewHint
|
|
2539
|
-
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2861
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2540
2862
|
style: {
|
|
2541
2863
|
...S.field,
|
|
2542
2864
|
flexDirection: "row",
|
|
@@ -2551,7 +2873,65 @@ function WorkersPanel({ ctx }) {
|
|
|
2551
2873
|
style: { fontSize: 12.5 },
|
|
2552
2874
|
children: copy.autoReview
|
|
2553
2875
|
})]
|
|
2554
|
-
}),
|
|
2876
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2877
|
+
style: S.field,
|
|
2878
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2879
|
+
style: S.fieldLabel,
|
|
2880
|
+
children: copy.reviewGate
|
|
2881
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2882
|
+
value: config.review?.gate ?? "required",
|
|
2883
|
+
onChange: (v) => reviewField({ gate: v }),
|
|
2884
|
+
options: [
|
|
2885
|
+
"required",
|
|
2886
|
+
"optional",
|
|
2887
|
+
"off"
|
|
2888
|
+
].map((value) => ({
|
|
2889
|
+
value,
|
|
2890
|
+
label: copy.reviewGateDesc[value]
|
|
2891
|
+
}))
|
|
2892
|
+
})]
|
|
2893
|
+
})] })),
|
|
2894
|
+
block({
|
|
2895
|
+
t: copy.modelOrdering,
|
|
2896
|
+
d: copy.orderingHint
|
|
2897
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: ["worker", "review"].map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2898
|
+
style: { display: "contents" },
|
|
2899
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2900
|
+
style: S.field,
|
|
2901
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2902
|
+
style: S.fieldLabel,
|
|
2903
|
+
children: [
|
|
2904
|
+
role === "worker" ? copy.roleWorker : copy.roleReviewer,
|
|
2905
|
+
" · ",
|
|
2906
|
+
copy.modelOrdering
|
|
2907
|
+
]
|
|
2908
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2909
|
+
value: config?.[role]?.model_policy?.ordering ?? "manual",
|
|
2910
|
+
onChange: (v) => rolePolicyField(role, { ordering: v }),
|
|
2911
|
+
options: ["manual", "health-aware"].map((value) => ({
|
|
2912
|
+
value,
|
|
2913
|
+
label: copy.orderingDesc[value]
|
|
2914
|
+
}))
|
|
2915
|
+
})]
|
|
2916
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2917
|
+
style: S.field,
|
|
2918
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2919
|
+
style: S.fieldLabel,
|
|
2920
|
+
children: [
|
|
2921
|
+
role === "worker" ? copy.roleWorker : copy.roleReviewer,
|
|
2922
|
+
" · ",
|
|
2923
|
+
copy.healthGate
|
|
2924
|
+
]
|
|
2925
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2926
|
+
value: config?.[role]?.model_policy?.health_gate ?? "hard-failures",
|
|
2927
|
+
onChange: (v) => rolePolicyField(role, { health_gate: v }),
|
|
2928
|
+
options: ["hard-failures", "off"].map((value) => ({
|
|
2929
|
+
value,
|
|
2930
|
+
label: copy.healthGateDesc[value]
|
|
2931
|
+
}))
|
|
2932
|
+
})]
|
|
2933
|
+
})]
|
|
2934
|
+
}, role)) })),
|
|
2555
2935
|
block({
|
|
2556
2936
|
t: copy.isolation,
|
|
2557
2937
|
d: copy.isolationHint
|
|
@@ -2948,11 +3328,7 @@ function WorkersPanel({ ctx }) {
|
|
|
2948
3328
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2949
3329
|
value: config.mode,
|
|
2950
3330
|
onChange: (v) => field("mode", v),
|
|
2951
|
-
options: [
|
|
2952
|
-
"auto",
|
|
2953
|
-
"hub",
|
|
2954
|
-
"standalone"
|
|
2955
|
-
].map((m) => ({
|
|
3331
|
+
options: ["auto", "hub"].map((m) => ({
|
|
2956
3332
|
value: m,
|
|
2957
3333
|
label: copy.modeDesc[m]
|
|
2958
3334
|
}))
|
|
@@ -3190,6 +3566,167 @@ function WorkersPanel({ ctx }) {
|
|
|
3190
3566
|
})
|
|
3191
3567
|
] }))
|
|
3192
3568
|
}),
|
|
3569
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
|
|
3570
|
+
sectionId: "harnessProviders",
|
|
3571
|
+
title: copy.sectionNames.harnessProviders,
|
|
3572
|
+
summary: sectionSummary(copy.providerCount(providerInventory?.records?.length ?? 0), providerInventoryError ? copy.providerLifecycleError : ""),
|
|
3573
|
+
expanded: !!expandedSections.harnessProviders,
|
|
3574
|
+
onToggle: () => toggleSection("harnessProviders"),
|
|
3575
|
+
children: [
|
|
3576
|
+
providerInventoryError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3577
|
+
style: {
|
|
3578
|
+
fontSize: 12,
|
|
3579
|
+
color: "#c55"
|
|
3580
|
+
},
|
|
3581
|
+
children: providerInventoryError
|
|
3582
|
+
}),
|
|
3583
|
+
!providerInventoryError && (providerInventory?.records?.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3584
|
+
style: {
|
|
3585
|
+
fontSize: 12,
|
|
3586
|
+
opacity: .55
|
|
3587
|
+
},
|
|
3588
|
+
children: copy.providerNoInventory
|
|
3589
|
+
}),
|
|
3590
|
+
(providerInventory?.records ?? []).map((record) => {
|
|
3591
|
+
const blocked = record.ownership === "harness" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0;
|
|
3592
|
+
const lifecycle = record.desired_state === "absent" ? "absent" : record.lifecycle?.enabled === false ? "disabled" : record.lifecycle?.catalogued ? "catalogued" : "configured";
|
|
3593
|
+
const rollbackTransaction = (providerInventory?.lifecycle_transactions ?? []).find((entry) => entry.provider_id === record.id && ["VERIFIED", "RESTART_PENDING"].includes(entry.state));
|
|
3594
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3595
|
+
style: {
|
|
3596
|
+
display: "flex",
|
|
3597
|
+
alignItems: "center",
|
|
3598
|
+
gap: 8,
|
|
3599
|
+
fontSize: 12.5,
|
|
3600
|
+
flexWrap: "wrap"
|
|
3601
|
+
},
|
|
3602
|
+
children: [
|
|
3603
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3604
|
+
style: { fontWeight: 600 },
|
|
3605
|
+
children: record.display_name || record.id
|
|
3606
|
+
}),
|
|
3607
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3608
|
+
style: {
|
|
3609
|
+
...S.chip(record.lifecycle?.enabled === true),
|
|
3610
|
+
...S.mono
|
|
3611
|
+
},
|
|
3612
|
+
children: record.id
|
|
3613
|
+
}),
|
|
3614
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3615
|
+
style: { opacity: .65 },
|
|
3616
|
+
children: copy.providerState(lifecycle)
|
|
3617
|
+
}),
|
|
3618
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3619
|
+
style: { opacity: .65 },
|
|
3620
|
+
children: copy.providerModels(record.models?.length ?? 0)
|
|
3621
|
+
}),
|
|
3622
|
+
Number(record.references?.active_jobs ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3623
|
+
style: { color: "#c98735" },
|
|
3624
|
+
children: copy.providerJobs(record.references.active_jobs)
|
|
3625
|
+
}),
|
|
3626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
3627
|
+
record.ownership !== "harness" && record.desired_state !== "absent" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3628
|
+
type: "button",
|
|
3629
|
+
style: {
|
|
3630
|
+
...S.btn,
|
|
3631
|
+
padding: "2px 8px"
|
|
3632
|
+
},
|
|
3633
|
+
disabled: blocked || providerLifecycleBusy !== null,
|
|
3634
|
+
onClick: () => {
|
|
3635
|
+
deleteHarnessProvider(record);
|
|
3636
|
+
},
|
|
3637
|
+
children: providerLifecycleBusy === record.id ? copy.working : copy.providerDeletePlan
|
|
3638
|
+
}),
|
|
3639
|
+
record.desired_state === "absent" && rollbackTransaction && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3640
|
+
type: "button",
|
|
3641
|
+
style: {
|
|
3642
|
+
...S.btn,
|
|
3643
|
+
padding: "2px 8px"
|
|
3644
|
+
},
|
|
3645
|
+
disabled: providerLifecycleBusy !== null,
|
|
3646
|
+
onClick: () => {
|
|
3647
|
+
rollbackHarnessProvider(record, rollbackTransaction.transaction_id);
|
|
3648
|
+
},
|
|
3649
|
+
children: providerLifecycleBusy === `rollback:${record.id}` ? copy.working : copy.providerRollback
|
|
3650
|
+
})
|
|
3651
|
+
]
|
|
3652
|
+
}, record.id);
|
|
3653
|
+
}),
|
|
3654
|
+
(credentialRefs.length > 0 || credentialUnverified.length > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3655
|
+
style: {
|
|
3656
|
+
marginTop: 10,
|
|
3657
|
+
borderTop: "1px solid rgba(128,128,128,0.16)",
|
|
3658
|
+
paddingTop: 8
|
|
3659
|
+
},
|
|
3660
|
+
children: [
|
|
3661
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3662
|
+
style: {
|
|
3663
|
+
fontWeight: 600,
|
|
3664
|
+
fontSize: 12.5
|
|
3665
|
+
},
|
|
3666
|
+
children: copy.credentialReferences
|
|
3667
|
+
}),
|
|
3668
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3669
|
+
style: {
|
|
3670
|
+
fontSize: 11,
|
|
3671
|
+
opacity: .6,
|
|
3672
|
+
margin: "3px 0 7px"
|
|
3673
|
+
},
|
|
3674
|
+
children: copy.credentialReferenceHint
|
|
3675
|
+
}),
|
|
3676
|
+
credentialUnverified.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3677
|
+
style: {
|
|
3678
|
+
color: "#c55",
|
|
3679
|
+
fontSize: 11.5
|
|
3680
|
+
},
|
|
3681
|
+
children: [
|
|
3682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3683
|
+
style: S.mono,
|
|
3684
|
+
children: entry.reference_id
|
|
3685
|
+
}),
|
|
3686
|
+
" · ",
|
|
3687
|
+
copy.credentialPurgeUnverified
|
|
3688
|
+
]
|
|
3689
|
+
}, `unverified:${entry.reference_id}`)),
|
|
3690
|
+
credentialRefs.map((ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3691
|
+
style: {
|
|
3692
|
+
display: "flex",
|
|
3693
|
+
alignItems: "center",
|
|
3694
|
+
gap: 8,
|
|
3695
|
+
fontSize: 11.5,
|
|
3696
|
+
flexWrap: "wrap"
|
|
3697
|
+
},
|
|
3698
|
+
children: [
|
|
3699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3700
|
+
style: S.mono,
|
|
3701
|
+
children: ref.reference_id
|
|
3702
|
+
}),
|
|
3703
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3704
|
+
style: { opacity: .65 },
|
|
3705
|
+
children: ref.ownership
|
|
3706
|
+
}),
|
|
3707
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3708
|
+
style: { color: ref.orphan ? "#c98735" : void 0 },
|
|
3709
|
+
children: ref.orphan ? copy.credentialOrphan : copy.credentialInUse(ref.consumer_count ?? 0)
|
|
3710
|
+
}),
|
|
3711
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
3712
|
+
ref.ownership === "crew-owned" && ref.orphan === true && ref.purge_capability === "eligible" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3713
|
+
type: "button",
|
|
3714
|
+
style: {
|
|
3715
|
+
...S.btn,
|
|
3716
|
+
padding: "2px 8px"
|
|
3717
|
+
},
|
|
3718
|
+
disabled: credentialLifecycleBusy !== null,
|
|
3719
|
+
onClick: () => {
|
|
3720
|
+
purgeCredentialRef(ref);
|
|
3721
|
+
},
|
|
3722
|
+
children: credentialLifecycleBusy === ref.reference_id ? copy.credentialPurging : copy.credentialPurgePlan
|
|
3723
|
+
})
|
|
3724
|
+
]
|
|
3725
|
+
}, ref.reference_id))
|
|
3726
|
+
]
|
|
3727
|
+
})
|
|
3728
|
+
]
|
|
3729
|
+
}),
|
|
3193
3730
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
3194
3731
|
sectionId: "providers",
|
|
3195
3732
|
title: copy.sectionNames.providers,
|