create-principles-disciple 1.102.0 → 1.103.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/console/dist/server/config/pd-config-store.d.ts +50 -1
- package/console/dist/server/config/pd-config-store.js +364 -1
- package/console/dist/server/models/ActivationsConsoleModel.d.ts +7 -4
- package/console/dist/server/models/ActivationsConsoleModel.js +7 -6
- package/console/dist/server/routes/config.js +92 -2
- package/console/dist/ui/api.d.ts +44 -3
- package/console/dist/ui/api.js +58 -2
- package/console/dist/ui/components/onboarding/DemoResultView.js +19 -1
- package/console/dist/ui/i18n/en.json +63 -2
- package/console/dist/ui/i18n/zh-CN.json +63 -2
- package/console/dist/ui/pages/activation/ActivationPage.js +11 -11
- package/console/dist/ui/pages/activation/ActivationValidators.js +14 -13
- package/console/dist/ui/pages/control-center/AgentCard.js +56 -7
- package/console/dist/ui/pages/control-center/ControlCenterPage.js +2 -1
- package/console/dist/ui/pages/control-center/RuntimeProfileManager.d.ts +31 -0
- package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +234 -0
- package/console/dist/ui/pages/debt/DebtPage.js +2 -2
- package/console/dist/ui/utils/validators.d.ts +26 -3
- package/console/dist/ui/utils/validators.js +23 -6
- package/console/dist/web/assets/app.css +41 -0
- package/console/dist/web/assets/app.js +1918 -1160
- package/core/dist/runtime-v2/__tests__/resolve-runtime-config-from-pd-config.test.js +8 -10
- package/core/dist/runtime-v2/__tests__/resolve-runtime-config-from-pd-config.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.d.ts +2 -0
- package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.js +81 -0
- package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.js.map +1 -0
- package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js +19 -0
- package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts +8 -0
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +4 -1
- package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
- package/core/dist/runtime-v2/candidate-intake-service.d.ts.map +1 -1
- package/core/dist/runtime-v2/candidate-intake-service.js +92 -6
- package/core/dist/runtime-v2/candidate-intake-service.js.map +1 -1
- package/core/dist/runtime-v2/candidate-intake.d.ts +38 -0
- package/core/dist/runtime-v2/candidate-intake.d.ts.map +1 -1
- package/core/dist/runtime-v2/candidate-intake.js +48 -0
- package/core/dist/runtime-v2/candidate-intake.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js +26 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +9 -5
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js +53 -33
- package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts +2 -0
- package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-agent-binding.js +3 -0
- package/core/dist/runtime-v2/config/pd-config-agent-binding.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +7 -3
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.js +27 -3
- package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.js +5 -2
- package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts +6 -0
- package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.js +11 -6
- package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +2 -1
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -0
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js +7 -0
- package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js +7 -6
- package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +1 -1
- package/core/dist/runtime-v2/internalization/diag-distiller-runner.js +4 -4
- package/core/dist/runtime-v2/internalization/diag-distiller-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js +5 -5
- package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/diag-router-runner.js +4 -4
- package/core/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +2 -0
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js +2 -0
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +5 -1
- package/dist/installer.js.map +1 -1
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +22 -10
- package/dist/mvp-config.js.map +1 -1
- package/package.json +1 -1
- package/pd-cli/dist/commands/__tests__/runtime-probe-config.test.js +2 -2
- package/pd-cli/dist/commands/__tests__/runtime-probe-config.test.js.map +1 -1
- package/pd-cli/dist/commands/demo-story-a.d.ts.map +1 -1
- package/pd-cli/dist/commands/demo-story-a.js +4 -2
- package/pd-cli/dist/commands/demo-story-a.js.map +1 -1
- package/pd-cli/dist/commands/proven-channel-baseline.d.ts.map +1 -1
- package/pd-cli/dist/commands/proven-channel-baseline.js +4 -2
- package/pd-cli/dist/commands/proven-channel-baseline.js.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.js +8 -2
- package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
- package/pd-cli/dist/commands/runtime-canary.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-canary.js +4 -2
- package/pd-cli/dist/commands/runtime-canary.js.map +1 -1
- package/pd-cli/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-health-snapshot.js +4 -2
- package/pd-cli/dist/commands/runtime-health-snapshot.js.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-integrity.js +4 -2
- package/pd-cli/dist/commands/runtime-internalization-integrity.js.map +1 -1
- package/pd-cli/dist/services/config-doctor.d.ts.map +1 -1
- package/pd-cli/dist/services/config-doctor.js +9 -2
- package/pd-cli/dist/services/config-doctor.js.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.js +1 -0
- package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/plugin/dist/core/rule-host.d.ts +5 -2
- package/plugin/dist/core/rule-host.js +15 -2
- package/plugin/dist/hooks/gate.js +1 -1
- package/plugin/dist/service/evolution-dedup.d.ts +1 -1
- package/plugin/dist/service/evolution-worker.d.ts +7 -31
- package/plugin/dist/service/evolution-worker.js +17 -36
- package/plugin/dist/service/queue-io.js +14 -2
- package/plugin/dist/service/queue-migration.d.ts +27 -26
- package/plugin/dist/service/queue-migration.js +57 -0
- package/plugin/templates/langs/en/core/AGENTS.md +2 -2
- package/plugin/templates/langs/en/core/TOOLS.md +1 -1
- package/plugin/templates/langs/zh/core/AGENTS.md +2 -2
- package/plugin/templates/langs/zh/core/TOOLS.md +1 -1
- package/templates/langs/en/core/AGENTS.md +2 -2
- package/templates/langs/en/core/TOOLS.md +1 -1
- package/templates/langs/zh/core/AGENTS.md +2 -2
- package/templates/langs/zh/core/TOOLS.md +1 -1
- package/plugin/dist/constants/diagnostician.d.ts +0 -12
- package/plugin/dist/constants/diagnostician.js +0 -65
- package/plugin/dist/service/evolution-queue-migration.d.ts +0 -128
- package/plugin/dist/service/evolution-queue-migration.js +0 -53
- package/plugin/templates/langs/en/skills/pd-diagnostician/SKILL.md +0 -466
- package/plugin/templates/langs/zh/skills/pd-diagnostician/SKILL.md +0 -391
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +0 -287
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
import { useNavigate } from "react-router-dom";
|
|
5
5
|
import { ChevronRight } from "lucide-react";
|
|
6
6
|
import { cn } from "../../../lib/utils.js";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { fetchAgentReadiness } from "../../api.js";
|
|
8
|
+
/** 状态点样式:ready / notready / off
|
|
9
|
+
*
|
|
10
|
+
* 接受 readiness + enabled 而非整个 agent 对象,以便 L1 状态点可以使用
|
|
11
|
+
* 动态获取的 readiness(fetchAgentReadiness)替换静态悲观值。
|
|
12
|
+
*/
|
|
13
|
+
function statusDotClasses(readiness, enabled) {
|
|
9
14
|
const base = "w-2 h-2 rounded-full border-2 shrink-0";
|
|
10
|
-
if (!
|
|
15
|
+
if (!enabled) {
|
|
11
16
|
return cn(base, "border-ink-4 bg-surface opacity-50");
|
|
12
17
|
}
|
|
13
|
-
switch (
|
|
18
|
+
switch (readiness) {
|
|
14
19
|
case "ready":
|
|
15
20
|
return cn(base, "border-green bg-green");
|
|
16
21
|
case "not_ready":
|
|
@@ -75,6 +80,44 @@ export function AgentCard({ agent, meta, profiles, onBindingChange, saving, loca
|
|
|
75
80
|
const [isOpen, setIsOpen] = useState(false);
|
|
76
81
|
const [showTechDetail, setShowTechDetail] = useState(false);
|
|
77
82
|
const [confirming, setConfirming] = useState(false);
|
|
83
|
+
// 动态 readiness:L2 展开时,若静态 readiness 为 'unknown'(悲观值),
|
|
84
|
+
// 调用 /api/v1/config/readiness/:agentName 获取实时探测结果替换静态值。
|
|
85
|
+
// 仅在 agent.enabled && agent.readiness === 'unknown' 时发起请求。
|
|
86
|
+
const [dynamicReadiness, setDynamicReadiness] = useState(null);
|
|
87
|
+
const [readinessLoading, setReadinessLoading] = useState(false);
|
|
88
|
+
const [readinessFetched, setReadinessFetched] = useState(false);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!isOpen)
|
|
91
|
+
return;
|
|
92
|
+
if (readinessFetched)
|
|
93
|
+
return;
|
|
94
|
+
if (!agent.enabled)
|
|
95
|
+
return;
|
|
96
|
+
if (agent.readiness !== "unknown")
|
|
97
|
+
return;
|
|
98
|
+
let cancelled = false;
|
|
99
|
+
setReadinessFetched(true);
|
|
100
|
+
setReadinessLoading(true);
|
|
101
|
+
fetchAgentReadiness(agent.name).then((result) => {
|
|
102
|
+
if (cancelled)
|
|
103
|
+
return;
|
|
104
|
+
setReadinessLoading(false);
|
|
105
|
+
if (result.success && result.data) {
|
|
106
|
+
setDynamicReadiness(result.data.readiness);
|
|
107
|
+
}
|
|
108
|
+
// rc-9: 调用失败时不静默吞错——result.error 已由 request() 提取;
|
|
109
|
+
// 此处 fallback 到静态 agent.readiness('unknown'),状态点保持 amber。
|
|
110
|
+
// loading 指示器消失即告知用户探测已结束但未获得确定结果。
|
|
111
|
+
});
|
|
112
|
+
return () => {
|
|
113
|
+
cancelled = true;
|
|
114
|
+
};
|
|
115
|
+
}, [isOpen, readinessFetched, agent.enabled, agent.readiness, agent.name]);
|
|
116
|
+
// 有效 readiness:静态值为 'unknown' 且已获取动态值时使用动态值;
|
|
117
|
+
// 否则使用静态值(父组件 re-fetch 后若不再是 'unknown' 则自动优先)。
|
|
118
|
+
const effectiveReadiness = agent.readiness === "unknown" && dynamicReadiness !== null
|
|
119
|
+
? dynamicReadiness
|
|
120
|
+
: agent.readiness;
|
|
78
121
|
// locale 决定取 Zh 还是 En 字段
|
|
79
122
|
const isZh = locale === "zh-CN";
|
|
80
123
|
const displayName = isZh ? meta.displayNameZh : meta.displayNameEn;
|
|
@@ -142,7 +185,7 @@ export function AgentCard({ agent, meta, profiles, onBindingChange, saving, loca
|
|
|
142
185
|
e.preventDefault();
|
|
143
186
|
setIsOpen((prev) => !prev);
|
|
144
187
|
}
|
|
145
|
-
}, children: [_jsx("div", { className: statusDotClasses(agent), "aria-hidden": "true" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: cn("text-[14px] font-semibold tracking-[-0.005em] flex items-center gap-[8px]", agent.enabled ? "text-ink" : "text-ink-3"), children: [_jsx("span", { children: displayName }), _jsx("span", { className: "font-mono text-[11.5px] text-ink-4 font-normal", children: agent.name })] }), _jsx("div", { className: "text-[12.5px] text-ink-3 mt-[2px] leading-[1.5]", children: role })] }), _jsx("button", { type: "button", "data-agent-toggle": true, onClick: handleToggleClick, disabled: isSaving, "aria-label": agent.enabled
|
|
188
|
+
}, children: [_jsx("div", { className: statusDotClasses(effectiveReadiness, agent.enabled), "aria-hidden": "true" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: cn("text-[14px] font-semibold tracking-[-0.005em] flex items-center gap-[8px]", agent.enabled ? "text-ink" : "text-ink-3"), children: [_jsx("span", { children: displayName }), _jsx("span", { className: "font-mono text-[11.5px] text-ink-4 font-normal", children: agent.name })] }), _jsx("div", { className: "text-[12.5px] text-ink-3 mt-[2px] leading-[1.5]", children: role })] }), _jsx("button", { type: "button", "data-agent-toggle": true, onClick: handleToggleClick, disabled: isSaving, "aria-label": agent.enabled
|
|
146
189
|
? t("pages.controlCenter.agentDisabled")
|
|
147
190
|
: t("pages.controlCenter.agentEnabled"), className: cn("rounded-[3px] px-[14px] py-[6px] text-[12.5px] font-medium transition-colors disabled:opacity-50", agent.enabled
|
|
148
191
|
? "border border-gov bg-gov text-paper hover:bg-gov-2"
|
|
@@ -159,5 +202,11 @@ export function AgentCard({ agent, meta, profiles, onBindingChange, saving, loca
|
|
|
159
202
|
e.stopPropagation();
|
|
160
203
|
setShowTechDetail((prev) => !prev);
|
|
161
204
|
}
|
|
162
|
-
}, children: [_jsx(ChevronRight, { className: cn("w-[12px] h-[12px] transition-transform", showTechDetail && "rotate-90"), "aria-hidden": "true" }), t("pages.controlCenter.techDetail")] }), showTechDetail && (_jsx("div", { className: "mt-2 grid grid-cols-[72px_1fr] gap-x-[14px] gap-y-[6px] text-[12px] leading-[1.55]", children: Object.entries(techDetail).map(([k, v]) => (_jsxs("div", { className: "contents", children: [_jsx("div", { className: "text-ink-4 font-mono text-[10.5px] uppercase tracking-[0.04em] pt-px", children: k }), _jsx("div", { className: "text-ink-3", children: renderTextWithCode(v) })] }, k))) }))] })), _jsxs("div", { className: "mt-[12px] pt-[12px] border-t border-line-2 flex items-center gap-[10px] text-[12px]", children: [_jsx("span", { className: "text-ink-4 font-mono text-[10.5px] uppercase tracking-[0.04em]", children: t("pages.controlCenter.profileLabelShort") }), _jsx("select", { value: agent.runtimeProfileId, onChange: handleProfileChange, disabled: isSaving, className: "font-mono text-[11px] text-ink-2 bg-surface border border-line-2 rounded-[3px] px-2 py-1 cursor-pointer focus:outline-none focus:border-gov disabled:opacity-50", "aria-label": t("pages.controlCenter.profileLabel"), children: profiles.map((p) => (_jsx("option", { value: p.id, children: p.label }, p.id))) })
|
|
205
|
+
}, children: [_jsx(ChevronRight, { className: cn("w-[12px] h-[12px] transition-transform", showTechDetail && "rotate-90"), "aria-hidden": "true" }), t("pages.controlCenter.techDetail")] }), showTechDetail && (_jsx("div", { className: "mt-2 grid grid-cols-[72px_1fr] gap-x-[14px] gap-y-[6px] text-[12px] leading-[1.55]", children: Object.entries(techDetail).map(([k, v]) => (_jsxs("div", { className: "contents", children: [_jsx("div", { className: "text-ink-4 font-mono text-[10.5px] uppercase tracking-[0.04em] pt-px", children: k }), _jsx("div", { className: "text-ink-3", children: renderTextWithCode(v) })] }, k))) }))] })), _jsxs("div", { className: "mt-[12px] pt-[12px] border-t border-line-2 flex items-center gap-[10px] text-[12px] flex-wrap", children: [_jsx("span", { className: "text-ink-4 font-mono text-[10.5px] uppercase tracking-[0.04em]", children: t("pages.controlCenter.profileLabelShort") }), _jsx("select", { value: agent.runtimeProfileId, onChange: handleProfileChange, disabled: isSaving, className: "font-mono text-[11px] text-ink-2 bg-surface border border-line-2 rounded-[3px] px-2 py-1 cursor-pointer focus:outline-none focus:border-gov disabled:opacity-50", "aria-label": t("pages.controlCenter.profileLabel"), children: profiles.map((p) => (_jsx("option", { value: p.id, children: p.label }, p.id))) }), readinessLoading && (_jsx("span", { className: "text-[10.5px] text-ink-4 font-mono animate-pulse", children: t("pages.controlCenter.readinessChecking") })), _jsx("button", { type: "button", "data-manage-profiles": true, onClick: (e) => {
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
const el = document.getElementById("runtime-profile-manager");
|
|
208
|
+
if (el) {
|
|
209
|
+
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
210
|
+
}
|
|
211
|
+
}, className: "ml-auto text-[11px] text-ink-4 hover:text-ink-2 transition-colors px-[8px] py-[3px] border border-line-2 rounded-[3px] focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "aria-label": t("pages.controlCenter.manageProfiles"), children: t("pages.controlCenter.manageProfiles") })] }), confirming && (_jsxs("div", { "data-confirm-bar": true, className: "mt-[10px] px-[12px] py-[10px] border border-danger rounded-[4px] bg-danger/10 text-[12px] text-ink-2 leading-[1.55]", children: [_jsx("div", { children: impact }), _jsxs("div", { className: "mt-2 flex gap-[8px]", children: [_jsx("button", { type: "button", onClick: handleConfirmOff, className: "text-[11.5px] px-[12px] py-[4px] rounded-[3px] border border-transparent bg-danger text-paper font-medium hover:opacity-90 transition-opacity focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.controlCenter.impact.confirmAck") }), _jsx("button", { type: "button", onClick: handleCancelConfirm, className: "text-[11.5px] px-[12px] py-[4px] rounded-[3px] border border-line bg-transparent text-ink-3 hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.controlCenter.impact.confirmCancel") })] })] }))] }))] }));
|
|
163
212
|
}
|
|
@@ -11,6 +11,7 @@ import { enumLabel } from "../../utils/enum-labels.js";
|
|
|
11
11
|
import { EmpathyObserverCostHint } from "./EmpathyObserverCostHint.js";
|
|
12
12
|
import { WorkflowDiagram } from "./WorkflowDiagram.js";
|
|
13
13
|
import { AgentGroup } from "./AgentGroup.js";
|
|
14
|
+
import { RuntimeProfileManager } from "./RuntimeProfileManager.js";
|
|
14
15
|
import { AGENT_GROUPS, AGENT_METADATA, } from "../../utils/agent-metadata.js";
|
|
15
16
|
// ── Runtime validators (H section / ERR-001/005/009/013) ─────────────────────
|
|
16
17
|
function validateSource(raw) {
|
|
@@ -507,5 +508,5 @@ export function ControlCenterPage() {
|
|
|
507
508
|
count: unknown.length,
|
|
508
509
|
names: unknown.map((a) => a.name).join(", "),
|
|
509
510
|
}) }))] }));
|
|
510
|
-
})()] }), _jsxs("section", { className: "mt-8", "aria-labelledby": "section-default-runtime", children: [_jsx(SectionTitle, { id: "section-default-runtime", children: t("pages.controlCenter.defaultRuntime") }), _jsx(DefaultRuntimeSelector, { currentDefault: configData.defaultRuntime, profiles: availableProfiles, onDefaultChange: handleDefaultRuntimeChange, saving: savingDefault })] }), _jsx("section", { className: "mt-8", "aria-labelledby": "section-diagnostics", children: _jsx(AdvancedDiagnostics, { config: configData }) }), _jsx("p", { className: "mt-10 pt-[18px] border-t border-line-2 text-ink-4 text-[12px] leading-[1.6] max-w-[60ch] font-mono", children: t("pages.controlCenter.footerNote") })] }) }));
|
|
511
|
+
})()] }), _jsxs("section", { className: "mt-8", "aria-labelledby": "section-default-runtime", children: [_jsx(SectionTitle, { id: "section-default-runtime", children: t("pages.controlCenter.defaultRuntime") }), _jsx(DefaultRuntimeSelector, { currentDefault: configData.defaultRuntime, profiles: availableProfiles, onDefaultChange: handleDefaultRuntimeChange, saving: savingDefault })] }), _jsxs("section", { className: "mt-8", "aria-labelledby": "section-runtime-profiles", children: [_jsx(SectionTitle, { id: "section-runtime-profiles", children: t("pages.controlCenter.profiles.sectionTitle") }), _jsx(RuntimeProfileManager, { profiles: availableProfiles, defaultRuntimeId: configData.defaultRuntime, onMutated: loadData })] }), _jsx("section", { className: "mt-8", "aria-labelledby": "section-diagnostics", children: _jsx(AdvancedDiagnostics, { config: configData }) }), _jsx("p", { className: "mt-10 pt-[18px] border-t border-line-2 text-ink-4 text-[12px] leading-[1.6] max-w-[60ch] font-mono", children: t("pages.controlCenter.footerNote") })] }) }));
|
|
511
512
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RedactedRuntimeProfileSummary } from "../../api.js";
|
|
2
|
+
/**
|
|
3
|
+
* RuntimeProfileManager — runtime profile CRUD UI.
|
|
4
|
+
*
|
|
5
|
+
* Renders a table of all runtime profiles with Create / Edit / Delete actions.
|
|
6
|
+
* The form is an inline panel (not a modal) to match the subtle, restrained
|
|
7
|
+
* aesthetic of AgentCard. Delete uses an inline confirmation bar.
|
|
8
|
+
*
|
|
9
|
+
* rc 合规:
|
|
10
|
+
* - rc-1: profiles 已由父组件 (ControlCenterPage) 用 validateRuntimeProfileSummary
|
|
11
|
+
* 验证;本组件不再重新解析。
|
|
12
|
+
* - rc-2: 无 as 绕过 — 表单 state 用强类型 ProfileFormState,API 响应用
|
|
13
|
+
* validateRuntimeProfileMutation 校验。
|
|
14
|
+
* - rc-3: 必填字段 (id / pi-ai 的 provider/model/apiKeyEnv) 缺失时 fail loud,
|
|
15
|
+
* 显示内联校验错误,不发请求。
|
|
16
|
+
* - rc-9: API 失败时通过 toast + 内联错误消息展示原因,不静默。
|
|
17
|
+
*
|
|
18
|
+
* 品牌约束 (PRI-CR1 B.4.4):
|
|
19
|
+
* - 无 translateY / scale hover,无硬编码色值
|
|
20
|
+
* - 状态点色板与 AgentCard 一致 (ready=green, needs_setup/not_ready/unknown=amber,
|
|
21
|
+
* disabled=ink-4)
|
|
22
|
+
*/
|
|
23
|
+
interface RuntimeProfileManagerProps {
|
|
24
|
+
profiles: RedactedRuntimeProfileSummary[];
|
|
25
|
+
/** ID of the profile currently set as defaultRuntime (cannot be deleted). */
|
|
26
|
+
defaultRuntimeId: string;
|
|
27
|
+
/** Called after a successful create/update/delete so the parent re-fetches. */
|
|
28
|
+
onMutated: () => void;
|
|
29
|
+
}
|
|
30
|
+
export declare function RuntimeProfileManager({ profiles, defaultRuntimeId, onMutated, }: RuntimeProfileManagerProps): import("react/jsx-runtime").JSX.Element;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import { cn } from "../../../lib/utils.js";
|
|
6
|
+
import { enumLabel } from "../../utils/enum-labels.js";
|
|
7
|
+
import { createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, } from "../../api.js";
|
|
8
|
+
const EMPTY_FORM = {
|
|
9
|
+
id: "",
|
|
10
|
+
type: "openclaw",
|
|
11
|
+
provider: "",
|
|
12
|
+
model: "",
|
|
13
|
+
apiKeyEnv: "",
|
|
14
|
+
baseUrl: "",
|
|
15
|
+
source: "",
|
|
16
|
+
};
|
|
17
|
+
/** 状态点样式:与 AgentCard 一致 (ready=green, needs_setup/not_ready/unknown=amber, disabled=ink-4) */
|
|
18
|
+
function readinessDotClasses(readiness) {
|
|
19
|
+
const base = "w-2 h-2 rounded-full border-2 shrink-0";
|
|
20
|
+
switch (readiness) {
|
|
21
|
+
case "ready":
|
|
22
|
+
return cn(base, "border-green bg-green");
|
|
23
|
+
case "needs_setup":
|
|
24
|
+
case "not_ready":
|
|
25
|
+
case "unknown":
|
|
26
|
+
return cn(base, "border-amber bg-surface");
|
|
27
|
+
case "disabled":
|
|
28
|
+
return cn(base, "border-ink-4 bg-surface opacity-50");
|
|
29
|
+
default:
|
|
30
|
+
return cn(base, "border-ink-4 bg-surface");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the profile object for a create request.
|
|
35
|
+
* Only includes fields relevant to the chosen type; omits empty optionals.
|
|
36
|
+
*/
|
|
37
|
+
function buildCreateProfile(form) {
|
|
38
|
+
const profile = { type: form.type };
|
|
39
|
+
if (form.type === "pi-ai") {
|
|
40
|
+
profile.provider = form.provider.trim();
|
|
41
|
+
profile.model = form.model.trim();
|
|
42
|
+
profile.apiKeyEnv = form.apiKeyEnv.trim();
|
|
43
|
+
if (form.baseUrl.trim() !== "") {
|
|
44
|
+
profile.baseUrl = form.baseUrl.trim();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (form.source.trim() !== "")
|
|
49
|
+
profile.source = form.source.trim();
|
|
50
|
+
if (form.provider.trim() !== "")
|
|
51
|
+
profile.provider = form.provider.trim();
|
|
52
|
+
if (form.model.trim() !== "")
|
|
53
|
+
profile.model = form.model.trim();
|
|
54
|
+
}
|
|
55
|
+
return profile;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the patch object for an update request.
|
|
59
|
+
* Sends `type` (server allows same-type no-op, rejects type change) plus all
|
|
60
|
+
* non-empty type-relevant fields. Empty optionals are omitted so existing
|
|
61
|
+
* values are preserved (merge-patch semantics on the server).
|
|
62
|
+
*/
|
|
63
|
+
function buildUpdatePatch(form) {
|
|
64
|
+
const patch = { type: form.type };
|
|
65
|
+
if (form.type === "pi-ai") {
|
|
66
|
+
if (form.provider.trim() !== "")
|
|
67
|
+
patch.provider = form.provider.trim();
|
|
68
|
+
if (form.model.trim() !== "")
|
|
69
|
+
patch.model = form.model.trim();
|
|
70
|
+
if (form.apiKeyEnv.trim() !== "")
|
|
71
|
+
patch.apiKeyEnv = form.apiKeyEnv.trim();
|
|
72
|
+
if (form.baseUrl.trim() !== "")
|
|
73
|
+
patch.baseUrl = form.baseUrl.trim();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (form.source.trim() !== "")
|
|
77
|
+
patch.source = form.source.trim();
|
|
78
|
+
if (form.provider.trim() !== "")
|
|
79
|
+
patch.provider = form.provider.trim();
|
|
80
|
+
if (form.model.trim() !== "")
|
|
81
|
+
patch.model = form.model.trim();
|
|
82
|
+
}
|
|
83
|
+
return patch;
|
|
84
|
+
}
|
|
85
|
+
/** Pre-fill form from an existing profile summary (edit mode). */
|
|
86
|
+
function formFromProfile(p) {
|
|
87
|
+
// type is validated to be a known ProfileType by the caller before invoking;
|
|
88
|
+
// here we guard defensively without `as`.
|
|
89
|
+
const type = p.type === "pi-ai" ? "pi-ai" : "openclaw";
|
|
90
|
+
return {
|
|
91
|
+
id: p.id,
|
|
92
|
+
type,
|
|
93
|
+
provider: p.provider ?? "",
|
|
94
|
+
model: p.model ?? "",
|
|
95
|
+
apiKeyEnv: p.apiKeyEnv ?? "",
|
|
96
|
+
baseUrl: "",
|
|
97
|
+
source: "",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function RuntimeProfileManager({ profiles, defaultRuntimeId, onMutated, }) {
|
|
101
|
+
const { t } = useTranslation();
|
|
102
|
+
const [mode, setMode] = useState("closed");
|
|
103
|
+
const [form, setForm] = useState(EMPTY_FORM);
|
|
104
|
+
const [formError, setFormError] = useState(null);
|
|
105
|
+
const [submitting, setSubmitting] = useState(false);
|
|
106
|
+
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
107
|
+
const [deleting, setDeleting] = useState(false);
|
|
108
|
+
const [deleteError, setDeleteError] = useState(null);
|
|
109
|
+
const openCreate = () => {
|
|
110
|
+
setForm(EMPTY_FORM);
|
|
111
|
+
setFormError(null);
|
|
112
|
+
setMode("create");
|
|
113
|
+
};
|
|
114
|
+
const openEdit = (p) => {
|
|
115
|
+
setForm(formFromProfile(p));
|
|
116
|
+
setFormError(null);
|
|
117
|
+
setMode("edit");
|
|
118
|
+
};
|
|
119
|
+
const closeForm = () => {
|
|
120
|
+
setMode("closed");
|
|
121
|
+
setForm(EMPTY_FORM);
|
|
122
|
+
setFormError(null);
|
|
123
|
+
};
|
|
124
|
+
const handleFieldChange = (key, value) => {
|
|
125
|
+
setForm((prev) => ({ ...prev, [key]: value }));
|
|
126
|
+
};
|
|
127
|
+
/** Validate the form; returns an error message key suffix or null if valid. */
|
|
128
|
+
function validate() {
|
|
129
|
+
if (mode === "create" && form.id.trim() === "") {
|
|
130
|
+
return t("pages.controlCenter.profiles.validationIdRequired");
|
|
131
|
+
}
|
|
132
|
+
if (form.type === "pi-ai") {
|
|
133
|
+
if (form.provider.trim() === "") {
|
|
134
|
+
return t("pages.controlCenter.profiles.validationProviderRequired");
|
|
135
|
+
}
|
|
136
|
+
if (form.model.trim() === "") {
|
|
137
|
+
return t("pages.controlCenter.profiles.validationModelRequired");
|
|
138
|
+
}
|
|
139
|
+
if (form.apiKeyEnv.trim() === "") {
|
|
140
|
+
return t("pages.controlCenter.profiles.validationApiKeyEnvRequired");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const handleSubmit = async (e) => {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
const validationError = validate();
|
|
148
|
+
if (validationError !== null) {
|
|
149
|
+
setFormError(validationError);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setFormError(null);
|
|
153
|
+
setSubmitting(true);
|
|
154
|
+
if (mode === "create") {
|
|
155
|
+
const result = await createRuntimeProfile(form.id.trim(), buildCreateProfile(form));
|
|
156
|
+
setSubmitting(false);
|
|
157
|
+
if (!result.success) {
|
|
158
|
+
// rc-9: surface server error (e.g. "already exists", "validation failed")
|
|
159
|
+
setFormError(result.error);
|
|
160
|
+
toast.error(t("pages.controlCenter.profiles.operationFailed"));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
toast.success(t("pages.controlCenter.profiles.createSuccess"));
|
|
164
|
+
closeForm();
|
|
165
|
+
onMutated();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// edit mode
|
|
169
|
+
const result = await updateRuntimeProfile(form.id, buildUpdatePatch(form));
|
|
170
|
+
setSubmitting(false);
|
|
171
|
+
if (!result.success) {
|
|
172
|
+
setFormError(result.error);
|
|
173
|
+
toast.error(t("pages.controlCenter.profiles.operationFailed"));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
toast.success(t("pages.controlCenter.profiles.updateSuccess"));
|
|
177
|
+
closeForm();
|
|
178
|
+
onMutated();
|
|
179
|
+
};
|
|
180
|
+
const handleDeleteConfirm = async () => {
|
|
181
|
+
if (deleteTarget === null)
|
|
182
|
+
return;
|
|
183
|
+
setDeleting(true);
|
|
184
|
+
setDeleteError(null);
|
|
185
|
+
const result = await deleteRuntimeProfile(deleteTarget);
|
|
186
|
+
setDeleting(false);
|
|
187
|
+
if (!result.success) {
|
|
188
|
+
// rc-9: surface reason (e.g. "referenced by agents: diagnostician")
|
|
189
|
+
setDeleteError(result.error);
|
|
190
|
+
toast.error(t("pages.controlCenter.profiles.operationFailed"));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
toast.success(t("pages.controlCenter.profiles.deleteSuccess"));
|
|
194
|
+
setDeleteTarget(null);
|
|
195
|
+
setDeleteError(null);
|
|
196
|
+
onMutated();
|
|
197
|
+
};
|
|
198
|
+
const handleDeleteCancel = () => {
|
|
199
|
+
setDeleteTarget(null);
|
|
200
|
+
setDeleteError(null);
|
|
201
|
+
};
|
|
202
|
+
const inputClass = "bg-surface border border-line-2 rounded-[3px] px-[10px] py-[6px] text-[12.5px] text-ink focus:outline-none focus:border-gov disabled:opacity-50 disabled:cursor-not-allowed w-full";
|
|
203
|
+
const labelClass = "block font-mono text-[10.5px] uppercase tracking-[0.04em] text-ink-4 mb-[4px]";
|
|
204
|
+
const hintClass = "mt-[3px] text-[11px] text-ink-4 leading-[1.4]";
|
|
205
|
+
return (_jsxs("div", { id: "runtime-profile-manager", className: "scroll-mt-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-[12px] mb-3", children: [_jsx("p", { className: "text-ink-3 text-[13px] leading-relaxed", children: t("pages.controlCenter.profiles.sectionDescription") }), mode === "closed" && (_jsx("button", { type: "button", onClick: openCreate, className: "shrink-0 border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] font-medium hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.controlCenter.profiles.createButton") }))] }), mode !== "closed" && (_jsxs("form", { onSubmit: handleSubmit, className: "mb-[10px] bg-panel border border-line rounded-[6px] px-[16px] py-[14px]", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h4", { className: "text-[13px] font-semibold text-ink", children: mode === "create"
|
|
206
|
+
? t("pages.controlCenter.profiles.formTitleCreate")
|
|
207
|
+
: t("pages.controlCenter.profiles.formTitleEdit", {
|
|
208
|
+
id: form.id,
|
|
209
|
+
}) }), _jsx("button", { type: "button", onClick: closeForm, className: "text-[12px] text-ink-4 hover:text-ink-2 transition-colors", children: t("pages.controlCenter.profiles.cancelButton") })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-[14px] gap-y-[12px]", children: [_jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-id", children: t("pages.controlCenter.profiles.fieldId") }), _jsx("input", { id: "rpf-id", type: "text", value: form.id, onChange: (e) => handleFieldChange("id", e.target.value), disabled: mode === "edit" || submitting, placeholder: t("pages.controlCenter.profiles.fieldIdPlaceholder"), className: inputClass, autoComplete: "off", spellCheck: false }), _jsx("p", { className: hintClass, children: t("pages.controlCenter.profiles.fieldIdHint") })] }), _jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-type", children: t("pages.controlCenter.profiles.fieldType") }), _jsxs("select", { id: "rpf-type", value: form.type, onChange: (e) => {
|
|
210
|
+
// rc-2: no `as` bypass — narrow via runtime check.
|
|
211
|
+
// The select only emits "openclaw" | "pi-ai", but we guard
|
|
212
|
+
// defensively so the value is provably ProfileType.
|
|
213
|
+
const v = e.target.value;
|
|
214
|
+
const typed = v === "pi-ai" ? "pi-ai" : "openclaw";
|
|
215
|
+
handleFieldChange("type", typed);
|
|
216
|
+
}, disabled: mode === "edit" || submitting, className: inputClass, children: [_jsx("option", { value: "openclaw", children: t("pages.controlCenter.profiles.typeOpenclaw") }), _jsx("option", { value: "pi-ai", children: t("pages.controlCenter.profiles.typePiAi") })] }), mode === "edit" && (_jsx("p", { className: hintClass, children: t("pages.controlCenter.profiles.fieldIdHint") }))] }), form.type === "pi-ai" && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-provider", children: t("pages.controlCenter.profiles.fieldProvider") }), _jsx("input", { id: "rpf-provider", type: "text", value: form.provider, onChange: (e) => handleFieldChange("provider", e.target.value), disabled: submitting, placeholder: t("pages.controlCenter.profiles.fieldProviderPlaceholder"), className: inputClass, autoComplete: "off", spellCheck: false })] }), _jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-model", children: t("pages.controlCenter.profiles.fieldModel") }), _jsx("input", { id: "rpf-model", type: "text", value: form.model, onChange: (e) => handleFieldChange("model", e.target.value), disabled: submitting, placeholder: t("pages.controlCenter.profiles.fieldModelPlaceholder"), className: inputClass, autoComplete: "off", spellCheck: false })] }), _jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-apikeyenv", children: t("pages.controlCenter.profiles.fieldApiKeyEnv") }), _jsx("input", { id: "rpf-apikeyenv", type: "text", value: form.apiKeyEnv, onChange: (e) => handleFieldChange("apiKeyEnv", e.target.value), disabled: submitting, placeholder: t("pages.controlCenter.profiles.fieldApiKeyEnvPlaceholder"), className: cn(inputClass, "font-mono"), autoComplete: "off", spellCheck: false }), _jsx("p", { className: hintClass, children: t("pages.controlCenter.profiles.fieldApiKeyEnvHint") })] }), _jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-baseurl", children: t("pages.controlCenter.profiles.fieldBaseUrl") }), _jsx("input", { id: "rpf-baseurl", type: "text", value: form.baseUrl, onChange: (e) => handleFieldChange("baseUrl", e.target.value), disabled: submitting, placeholder: t("pages.controlCenter.profiles.fieldBaseUrlPlaceholder"), className: inputClass, autoComplete: "off", spellCheck: false })] })] })), form.type === "openclaw" && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-source", children: t("pages.controlCenter.profiles.fieldSource") }), _jsx("input", { id: "rpf-source", type: "text", value: form.source, onChange: (e) => handleFieldChange("source", e.target.value), disabled: submitting, placeholder: t("pages.controlCenter.profiles.fieldSourcePlaceholder"), className: inputClass, autoComplete: "off", spellCheck: false })] }), _jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-provider-oc", children: t("pages.controlCenter.profiles.fieldProvider") }), _jsx("input", { id: "rpf-provider-oc", type: "text", value: form.provider, onChange: (e) => handleFieldChange("provider", e.target.value), disabled: submitting, placeholder: t("pages.controlCenter.profiles.fieldProviderPlaceholder"), className: inputClass, autoComplete: "off", spellCheck: false })] }), _jsxs("div", { children: [_jsx("label", { className: labelClass, htmlFor: "rpf-model-oc", children: t("pages.controlCenter.profiles.fieldModel") }), _jsx("input", { id: "rpf-model-oc", type: "text", value: form.model, onChange: (e) => handleFieldChange("model", e.target.value), disabled: submitting, placeholder: t("pages.controlCenter.profiles.fieldModelPlaceholder"), className: inputClass, autoComplete: "off", spellCheck: false })] })] }))] }), formError !== null && (_jsx("div", { className: "mt-3 px-[12px] py-[8px] border border-red/35 rounded-[4px] bg-red/10 text-[12px] text-red font-mono", children: formError })), _jsxs("div", { className: "mt-3 flex items-center gap-[8px]", children: [_jsx("button", { type: "submit", disabled: submitting, className: "border border-gov bg-gov text-paper rounded-[3px] px-[14px] py-[6px] text-[12.5px] font-medium hover:bg-gov-2 transition-colors disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: submitting
|
|
217
|
+
? mode === "create"
|
|
218
|
+
? t("pages.controlCenter.profiles.creating")
|
|
219
|
+
: t("pages.controlCenter.profiles.saving")
|
|
220
|
+
: t("pages.controlCenter.profiles.saveButton") }), _jsx("button", { type: "button", onClick: closeForm, disabled: submitting, className: "border border-line bg-surface text-ink-3 rounded-[3px] px-[14px] py-[6px] text-[12.5px] font-medium hover:border-line-2 hover:text-ink transition-colors disabled:opacity-50", children: t("pages.controlCenter.profiles.cancelButton") })] })] })), profiles.length === 0 ? (_jsx("div", { className: "bg-surface border border-line-2 rounded-[4px] px-[16px] py-[14px] text-[12.5px] text-ink-4 italic", children: t("pages.controlCenter.profiles.empty") })) : (_jsxs("div", { className: "bg-surface border border-line-2 rounded-[4px] overflow-hidden", children: [_jsxs("div", { className: "grid grid-cols-[12px_1fr_84px_1fr_1fr_1fr_auto] gap-[10px] items-center px-[16px] py-[9px] bg-paper-2 border-b border-line-2 font-mono text-[10.5px] uppercase tracking-[0.04em] text-ink-4", children: [_jsx("span", {}), _jsx("span", { children: t("pages.controlCenter.profiles.columnId") }), _jsx("span", { children: t("pages.controlCenter.profiles.columnType") }), _jsx("span", { children: t("pages.controlCenter.profiles.columnProvider") }), _jsx("span", { children: t("pages.controlCenter.profiles.columnModel") }), _jsx("span", { children: t("pages.controlCenter.profiles.columnApiKeyEnv") }), _jsx("span", { className: "text-right", children: t("pages.controlCenter.profiles.columnActions") })] }), profiles.map((p) => {
|
|
221
|
+
const isDefault = p.id === defaultRuntimeId;
|
|
222
|
+
const isDeleteTarget = deleteTarget === p.id;
|
|
223
|
+
return (_jsxs("div", { className: "border-b border-line-2 last:border-b-0", children: [_jsxs("div", { className: "grid grid-cols-[12px_1fr_84px_1fr_1fr_1fr_auto] gap-[10px] items-center px-[16px] py-[10px] text-[12.5px]", children: [_jsx("div", { className: readinessDotClasses(p.readiness), "aria-label": enumLabel("readiness", p.readiness, t), title: enumLabel("readiness", p.readiness, t) }), _jsxs("div", { className: "min-w-0 flex items-center gap-[6px]", children: [_jsx("span", { className: "font-mono text-[12px] text-ink truncate", children: p.id }), isDefault && (_jsx("span", { className: "shrink-0 text-[9.5px] font-mono px-[5px] py-[1px] rounded-[2px] bg-gov/10 text-gov border border-gov/22 tracking-[0.02em]", children: "default" }))] }), _jsx("span", { className: "font-mono text-[11.5px] text-ink-3", children: p.type }), _jsx("span", { className: "text-ink-3 truncate", children: p.provider ?? "—" }), _jsx("span", { className: "text-ink-3 truncate", children: p.model ?? "—" }), _jsx("span", { className: "font-mono text-[11.5px] text-ink-3 truncate", children: p.apiKeyEnv ?? "—" }), _jsxs("div", { className: "flex items-center justify-end gap-[6px]", children: [_jsx("button", { type: "button", onClick: () => openEdit(p), disabled: mode !== "closed" || deleting, className: "text-[11.5px] px-[10px] py-[4px] rounded-[3px] border border-line bg-surface text-ink-3 hover:border-line-2 hover:text-ink transition-colors disabled:opacity-40 disabled:cursor-not-allowed", children: t("pages.controlCenter.profiles.editButton") }), _jsx("button", { type: "button", onClick: () => {
|
|
224
|
+
setDeleteTarget(p.id);
|
|
225
|
+
setDeleteError(null);
|
|
226
|
+
}, disabled: mode !== "closed" ||
|
|
227
|
+
deleting ||
|
|
228
|
+
deleteTarget !== null, className: "text-[11.5px] px-[10px] py-[4px] rounded-[3px] border border-line bg-surface text-ink-3 hover:border-red/40 hover:text-red transition-colors disabled:opacity-40 disabled:cursor-not-allowed", children: t("pages.controlCenter.profiles.deleteButton") })] })] }), isDeleteTarget && (_jsx("div", { className: "px-[16px] pb-[12px]", children: _jsxs("div", { className: "px-[12px] py-[10px] border border-danger rounded-[4px] bg-danger/10 text-[12px] text-ink-2 leading-[1.55]", children: [_jsx("div", { className: "font-medium", children: t("pages.controlCenter.profiles.deleteConfirmMessage", {
|
|
229
|
+
id: p.id,
|
|
230
|
+
}) }), isDefault && (_jsx("div", { className: "mt-1 text-ink-3", children: t("pages.controlCenter.profiles.sectionDescription") })), deleteError !== null && (_jsx("div", { className: "mt-2 font-mono text-[11.5px] text-red", children: deleteError })), _jsxs("div", { className: "mt-2 flex gap-[8px]", children: [_jsx("button", { type: "button", onClick: handleDeleteConfirm, disabled: deleting, className: "text-[11.5px] px-[12px] py-[4px] rounded-[3px] border border-transparent bg-danger text-paper font-medium hover:opacity-90 transition-opacity disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: deleting
|
|
231
|
+
? t("pages.controlCenter.profiles.deleting")
|
|
232
|
+
: t("pages.controlCenter.profiles.deleteConfirmAck") }), _jsx("button", { type: "button", onClick: handleDeleteCancel, disabled: deleting, className: "text-[11.5px] px-[12px] py-[4px] rounded-[3px] border border-line bg-transparent text-ink-3 hover:border-line-2 transition-colors disabled:opacity-50", children: t("pages.controlCenter.profiles.deleteConfirmCancel") })] })] }) }))] }, p.id));
|
|
233
|
+
})] }))] }));
|
|
234
|
+
}
|
|
@@ -49,7 +49,7 @@ function DebtCard({ item, onArchive, onDeactivate, isProcessing, }) {
|
|
|
49
49
|
const isStagnant = item.type === "stagnant";
|
|
50
50
|
return (_jsx("article", { className: "p-5 bg-panel border border-line rounded-[6px] hover:border-line-2 transition-colors relative", "aria-label": t("pages.debt.principleAriaLabel", { id: item.id }), children: _jsxs("div", { className: "flex flex-col md:flex-row md:items-start gap-4", children: [_jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-mono text-[11px] uppercase px-2 py-0.5 bg-line rounded text-ink-3", children: item.id }), _jsx("span", { className: `font-mono text-[11px] uppercase px-2 py-0.5 rounded ${isStagnant
|
|
51
51
|
? "bg-amber/10 text-amber"
|
|
52
|
-
: "bg-danger/10 text-danger"}`, children: isStagnant ? t("pages.focus.summaryStagnation") : t("pages.sidebar.debt") })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-ink text-[15px] font-medium leading-relaxed", children: item.text }), item.triggerPattern && (_jsxs("p", { className: "text-ink-3 text-[13px] font-mono leading-relaxed", children: [t("principles.detail.triggerLabel", { defaultValue: "Trigger:" }), " ", item.triggerPattern] }))] }), _jsxs("div", { className: "flex items-start gap-2 pt-1", children: [_jsx(AlertTriangle, { className: "w-4 h-4 text-amber shrink-0 mt-0.5", "aria-hidden": "true" }), _jsxs("div", { className: "text-[13px] leading-relaxed", children: [_jsxs("span", { className: "font-semibold text-ink-3", children: [t("pages.debt.suggestedAction"), ": "] }), renderReason()] })] })] }), _jsx("div", { className: "flex flex-col gap-2 shrink-0 md:w-48", children: !confirmAction ? (_jsxs(_Fragment, { children: [_jsxs("button", { onClick: handleArchiveClick, disabled: isProcessing, className: "w-full inline-flex items-center justify-center gap-1.5 border border-line bg-surface text-ink hover:border-line-2 transition-colors rounded-[3px] px-[14px] py-[6px] text-[12.5px] focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `archive-btn-${item.id}`, children: [_jsx(Archive, { className: "w-4 h-4 text-ink-3", "aria-hidden": "true" }), t("pages.debt.actionArchive")] }), item.activeActivationRecords && item.activeActivationRecords.length > 0 ? (item.activeActivationRecords.map((ar) => (_jsxs("button", { onClick: () => handleDeactivateClick(ar.
|
|
52
|
+
: "bg-danger/10 text-danger"}`, children: isStagnant ? t("pages.focus.summaryStagnation") : t("pages.sidebar.debt") })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-ink text-[15px] font-medium leading-relaxed", children: item.text }), item.triggerPattern && (_jsxs("p", { className: "text-ink-3 text-[13px] font-mono leading-relaxed", children: [t("principles.detail.triggerLabel", { defaultValue: "Trigger:" }), " ", item.triggerPattern] }))] }), _jsxs("div", { className: "flex items-start gap-2 pt-1", children: [_jsx(AlertTriangle, { className: "w-4 h-4 text-amber shrink-0 mt-0.5", "aria-hidden": "true" }), _jsxs("div", { className: "text-[13px] leading-relaxed", children: [_jsxs("span", { className: "font-semibold text-ink-3", children: [t("pages.debt.suggestedAction"), ": "] }), renderReason()] })] })] }), _jsx("div", { className: "flex flex-col gap-2 shrink-0 md:w-48", children: !confirmAction ? (_jsxs(_Fragment, { children: [_jsxs("button", { onClick: handleArchiveClick, disabled: isProcessing, className: "w-full inline-flex items-center justify-center gap-1.5 border border-line bg-surface text-ink hover:border-line-2 transition-colors rounded-[3px] px-[14px] py-[6px] text-[12.5px] focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `archive-btn-${item.id}`, children: [_jsx(Archive, { className: "w-4 h-4 text-ink-3", "aria-hidden": "true" }), t("pages.debt.actionArchive")] }), item.activeActivationRecords && item.activeActivationRecords.length > 0 ? (item.activeActivationRecords.map((ar) => (_jsxs("button", { onClick: () => handleDeactivateClick(ar.activationId), disabled: isProcessing, className: "w-full inline-flex items-center justify-center gap-1.5 border border-danger/20 bg-surface text-danger hover:bg-danger/5 transition-colors rounded-[3px] px-[14px] py-[6px] text-[12.5px] focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `deactivate-btn-${ar.activationId}`, children: [_jsx(Trash2, { className: "w-4 h-4 text-danger/80", "aria-hidden": "true" }), t("pages.debt.actionDeactivate"), " (", ar.channel, ")"] }, ar.activationId)))) : (_jsx("button", { disabled: true, className: "w-full inline-flex items-center justify-center gap-1.5 border border-line bg-surface/50 text-ink-4 cursor-not-allowed rounded-[3px] px-[14px] py-[6px] text-[12.5px]", title: t("pages.debt.demoteTooltip"), children: t("pages.debt.actionKeep") }))] })) : (_jsxs("div", { className: "flex flex-col gap-2 p-3 bg-surface border border-amber/20 rounded-[4px] w-full", role: "alert", "data-testid": `confirm-bar-${item.id}`, children: [_jsx("p", { className: "text-ink-2 text-[12px] leading-relaxed", children: confirmAction === "archive"
|
|
53
53
|
? t("pages.debt.confirmArchive")
|
|
54
54
|
: t("pages.debt.confirmDeactivate") }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "default", size: "sm", onClick: handleConfirm, disabled: isProcessing, className: "flex-1 text-[12px] py-1", "data-testid": `confirm-btn-${item.id}`, children: t("common.confirm") }), _jsx(Button, { variant: "outline", size: "sm", onClick: handleCancel, className: "flex-1 text-[12px] py-1", children: t("common.cancel") })] })] })) })] }) }));
|
|
55
55
|
}
|
|
@@ -189,7 +189,7 @@ export function DebtPage() {
|
|
|
189
189
|
.map((item) => {
|
|
190
190
|
if (item.id !== principleId)
|
|
191
191
|
return item;
|
|
192
|
-
const updatedActivations = item.activeActivationRecords?.filter((ar) => ar.
|
|
192
|
+
const updatedActivations = item.activeActivationRecords?.filter((ar) => ar.activationId !== activationId);
|
|
193
193
|
return {
|
|
194
194
|
...item,
|
|
195
195
|
activeActivationRecords: updatedActivations,
|
|
@@ -146,6 +146,28 @@ export interface DefaultRuntimeUpdateData {
|
|
|
146
146
|
defaultRuntime: string;
|
|
147
147
|
}
|
|
148
148
|
export declare function validateDefaultRuntimeUpdate(v: unknown): DefaultRuntimeUpdateData | null;
|
|
149
|
+
/**
|
|
150
|
+
* Response shape for create/update/delete runtime profile endpoints.
|
|
151
|
+
*
|
|
152
|
+
* Server returns `{ profileId: string, profile: { type, provider?, model?, ... } }`.
|
|
153
|
+
* We validate the contract fields (profileId + profile.type) loudly and accept
|
|
154
|
+
* the rest of the profile object as a string-indexed record, since the patch
|
|
155
|
+
* surface is open (timeoutMs, maxRetries, baseUrl, source, etc.). The page only
|
|
156
|
+
* needs profileId to confirm the target; it re-fetches the catalog for display.
|
|
157
|
+
*
|
|
158
|
+
* rc-1: input treated as unknown.
|
|
159
|
+
* rc-2: no `as` bypass — uses Object.hasOwn + typeof guards.
|
|
160
|
+
* rc-3: profileId + profile.type are required and fail loud.
|
|
161
|
+
* rc-5: Object.hasOwn for untrusted keys.
|
|
162
|
+
*/
|
|
163
|
+
export interface RuntimeProfileMutationData {
|
|
164
|
+
profileId: string;
|
|
165
|
+
profile: {
|
|
166
|
+
type: string;
|
|
167
|
+
[key: string]: unknown;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export declare function validateRuntimeProfileMutation(v: unknown): RuntimeProfileMutationData | null;
|
|
149
171
|
interface HealthCheckItemData {
|
|
150
172
|
id: string;
|
|
151
173
|
name: string;
|
|
@@ -188,7 +210,7 @@ export interface GovernanceQueueData {
|
|
|
188
210
|
}
|
|
189
211
|
export declare function validateGovernanceQueue(v: unknown): GovernanceQueueData | null;
|
|
190
212
|
export interface ActivationRecordData {
|
|
191
|
-
|
|
213
|
+
activationId: string;
|
|
192
214
|
artifactId: string;
|
|
193
215
|
principleId: string;
|
|
194
216
|
channel: string;
|
|
@@ -199,8 +221,9 @@ export interface ActivationRecordData {
|
|
|
199
221
|
}
|
|
200
222
|
export interface ActivationsData {
|
|
201
223
|
activations: ActivationRecordData[];
|
|
202
|
-
|
|
203
|
-
|
|
224
|
+
status: string;
|
|
225
|
+
reason?: string;
|
|
226
|
+
nextAction?: string;
|
|
204
227
|
}
|
|
205
228
|
export declare function validateActivations(v: unknown): ActivationsData | null;
|
|
206
229
|
export interface DisableActivationData {
|
|
@@ -402,6 +402,21 @@ export function validateDefaultRuntimeUpdate(v) {
|
|
|
402
402
|
return null;
|
|
403
403
|
return { defaultRuntime: v.defaultRuntime };
|
|
404
404
|
}
|
|
405
|
+
export function validateRuntimeProfileMutation(v) {
|
|
406
|
+
if (!isObject(v))
|
|
407
|
+
return null;
|
|
408
|
+
if (!Object.hasOwn(v, 'profileId') || !isString(v.profileId))
|
|
409
|
+
return null;
|
|
410
|
+
if (!Object.hasOwn(v, 'profile') || !isObject(v.profile))
|
|
411
|
+
return null;
|
|
412
|
+
const { profile } = v;
|
|
413
|
+
if (!Object.hasOwn(profile, 'type') || !isString(profile.type))
|
|
414
|
+
return null;
|
|
415
|
+
// Bind narrowed string to a local so the return literal keeps `type: string`
|
|
416
|
+
// (spreading a Record<string, unknown> alone would widen `type` back to unknown).
|
|
417
|
+
const profileType = profile.type;
|
|
418
|
+
return { profileId: v.profileId, profile: { ...profile, type: profileType } };
|
|
419
|
+
}
|
|
405
420
|
function validateHealthCheckItem(v) {
|
|
406
421
|
if (!isObject(v))
|
|
407
422
|
return null;
|
|
@@ -557,7 +572,7 @@ export function validateGovernanceQueue(v) {
|
|
|
557
572
|
function validateActivationRecord(v) {
|
|
558
573
|
if (!isObject(v))
|
|
559
574
|
return null;
|
|
560
|
-
if (!Object.hasOwn(v, '
|
|
575
|
+
if (!Object.hasOwn(v, 'activationId') || !isString(v.activationId))
|
|
561
576
|
return null;
|
|
562
577
|
if (!Object.hasOwn(v, 'artifactId') || !isString(v.artifactId))
|
|
563
578
|
return null;
|
|
@@ -575,7 +590,7 @@ function validateActivationRecord(v) {
|
|
|
575
590
|
if (!Object.hasOwn(v, 'status') || !isString(v.status))
|
|
576
591
|
return null;
|
|
577
592
|
return {
|
|
578
|
-
|
|
593
|
+
activationId: v.activationId, artifactId: v.artifactId, principleId: v.principleId,
|
|
579
594
|
channel: v.channel, action: v.action, targetRef: v.targetRef,
|
|
580
595
|
activatedAt: activatedAt.value,
|
|
581
596
|
status: v.status,
|
|
@@ -586,14 +601,16 @@ export function validateActivations(v) {
|
|
|
586
601
|
return null;
|
|
587
602
|
if (!Object.hasOwn(v, 'activations') || !Array.isArray(v.activations))
|
|
588
603
|
return null;
|
|
589
|
-
if (!Object.hasOwn(v, '
|
|
604
|
+
if (!Object.hasOwn(v, 'status') || !isString(v.status))
|
|
590
605
|
return null;
|
|
591
606
|
const activations = validateArray(v.activations, validateActivationRecord);
|
|
592
607
|
if (activations === null)
|
|
593
608
|
return null;
|
|
594
|
-
const result = { activations,
|
|
595
|
-
if (Object.hasOwn(v, '
|
|
596
|
-
result.
|
|
609
|
+
const result = { activations, status: v.status };
|
|
610
|
+
if (Object.hasOwn(v, 'reason') && isString(v.reason))
|
|
611
|
+
result.reason = v.reason;
|
|
612
|
+
if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction))
|
|
613
|
+
result.nextAction = v.nextAction;
|
|
597
614
|
return result;
|
|
598
615
|
}
|
|
599
616
|
export function validateDisableActivation(v) {
|