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.
Files changed (139) hide show
  1. package/console/dist/server/config/pd-config-store.d.ts +50 -1
  2. package/console/dist/server/config/pd-config-store.js +364 -1
  3. package/console/dist/server/models/ActivationsConsoleModel.d.ts +7 -4
  4. package/console/dist/server/models/ActivationsConsoleModel.js +7 -6
  5. package/console/dist/server/routes/config.js +92 -2
  6. package/console/dist/ui/api.d.ts +44 -3
  7. package/console/dist/ui/api.js +58 -2
  8. package/console/dist/ui/components/onboarding/DemoResultView.js +19 -1
  9. package/console/dist/ui/i18n/en.json +63 -2
  10. package/console/dist/ui/i18n/zh-CN.json +63 -2
  11. package/console/dist/ui/pages/activation/ActivationPage.js +11 -11
  12. package/console/dist/ui/pages/activation/ActivationValidators.js +14 -13
  13. package/console/dist/ui/pages/control-center/AgentCard.js +56 -7
  14. package/console/dist/ui/pages/control-center/ControlCenterPage.js +2 -1
  15. package/console/dist/ui/pages/control-center/RuntimeProfileManager.d.ts +31 -0
  16. package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +234 -0
  17. package/console/dist/ui/pages/debt/DebtPage.js +2 -2
  18. package/console/dist/ui/utils/validators.d.ts +26 -3
  19. package/console/dist/ui/utils/validators.js +23 -6
  20. package/console/dist/web/assets/app.css +41 -0
  21. package/console/dist/web/assets/app.js +1918 -1160
  22. package/core/dist/runtime-v2/__tests__/resolve-runtime-config-from-pd-config.test.js +8 -10
  23. package/core/dist/runtime-v2/__tests__/resolve-runtime-config-from-pd-config.test.js.map +1 -1
  24. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.d.ts +2 -0
  25. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.d.ts.map +1 -0
  26. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.js +81 -0
  27. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.js.map +1 -0
  28. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js +19 -0
  29. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js.map +1 -1
  30. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts +8 -0
  31. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
  32. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +4 -1
  33. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  34. package/core/dist/runtime-v2/candidate-intake-service.d.ts.map +1 -1
  35. package/core/dist/runtime-v2/candidate-intake-service.js +92 -6
  36. package/core/dist/runtime-v2/candidate-intake-service.js.map +1 -1
  37. package/core/dist/runtime-v2/candidate-intake.d.ts +38 -0
  38. package/core/dist/runtime-v2/candidate-intake.d.ts.map +1 -1
  39. package/core/dist/runtime-v2/candidate-intake.js +48 -0
  40. package/core/dist/runtime-v2/candidate-intake.js.map +1 -1
  41. package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js +26 -1
  42. package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js.map +1 -1
  43. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +9 -5
  44. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
  45. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js +53 -33
  46. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js.map +1 -1
  47. package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts +2 -0
  48. package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts.map +1 -1
  49. package/core/dist/runtime-v2/config/pd-config-agent-binding.js +3 -0
  50. package/core/dist/runtime-v2/config/pd-config-agent-binding.js.map +1 -1
  51. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +7 -3
  52. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
  53. package/core/dist/runtime-v2/config/pd-config-defaults.js +27 -3
  54. package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
  55. package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
  56. package/core/dist/runtime-v2/config/pd-config-redaction.js +5 -2
  57. package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
  58. package/core/dist/runtime-v2/config/pd-config-types.d.ts +6 -0
  59. package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
  60. package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
  61. package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
  62. package/core/dist/runtime-v2/config/pd-config-validate.js +11 -6
  63. package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
  64. package/core/dist/runtime-v2/index.d.ts +2 -1
  65. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  66. package/core/dist/runtime-v2/index.js +1 -0
  67. package/core/dist/runtime-v2/index.js.map +1 -1
  68. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js +7 -0
  69. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js.map +1 -1
  70. package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js +7 -6
  71. package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js.map +1 -1
  72. package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +1 -1
  73. package/core/dist/runtime-v2/internalization/diag-distiller-runner.js +4 -4
  74. package/core/dist/runtime-v2/internalization/diag-distiller-runner.js.map +1 -1
  75. package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js +5 -5
  76. package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
  77. package/core/dist/runtime-v2/internalization/diag-router-runner.js +4 -4
  78. package/core/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
  79. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +2 -0
  80. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  81. package/core/dist/runtime-v2/pain-signal-runtime-factory.js +2 -0
  82. package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  83. package/dist/installer.d.ts.map +1 -1
  84. package/dist/installer.js +5 -1
  85. package/dist/installer.js.map +1 -1
  86. package/dist/mvp-config.d.ts.map +1 -1
  87. package/dist/mvp-config.js +22 -10
  88. package/dist/mvp-config.js.map +1 -1
  89. package/package.json +1 -1
  90. package/pd-cli/dist/commands/__tests__/runtime-probe-config.test.js +2 -2
  91. package/pd-cli/dist/commands/__tests__/runtime-probe-config.test.js.map +1 -1
  92. package/pd-cli/dist/commands/demo-story-a.d.ts.map +1 -1
  93. package/pd-cli/dist/commands/demo-story-a.js +4 -2
  94. package/pd-cli/dist/commands/demo-story-a.js.map +1 -1
  95. package/pd-cli/dist/commands/proven-channel-baseline.d.ts.map +1 -1
  96. package/pd-cli/dist/commands/proven-channel-baseline.js +4 -2
  97. package/pd-cli/dist/commands/proven-channel-baseline.js.map +1 -1
  98. package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
  99. package/pd-cli/dist/commands/runtime-activation.js +8 -2
  100. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  101. package/pd-cli/dist/commands/runtime-canary.d.ts.map +1 -1
  102. package/pd-cli/dist/commands/runtime-canary.js +4 -2
  103. package/pd-cli/dist/commands/runtime-canary.js.map +1 -1
  104. package/pd-cli/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
  105. package/pd-cli/dist/commands/runtime-health-snapshot.js +4 -2
  106. package/pd-cli/dist/commands/runtime-health-snapshot.js.map +1 -1
  107. package/pd-cli/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
  108. package/pd-cli/dist/commands/runtime-internalization-integrity.js +4 -2
  109. package/pd-cli/dist/commands/runtime-internalization-integrity.js.map +1 -1
  110. package/pd-cli/dist/services/config-doctor.d.ts.map +1 -1
  111. package/pd-cli/dist/services/config-doctor.js +9 -2
  112. package/pd-cli/dist/services/config-doctor.js.map +1 -1
  113. package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  114. package/pd-cli/dist/services/runtime-adapter-resolver.js +1 -0
  115. package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
  116. package/plugin/dist/core/rule-host.d.ts +5 -2
  117. package/plugin/dist/core/rule-host.js +15 -2
  118. package/plugin/dist/hooks/gate.js +1 -1
  119. package/plugin/dist/service/evolution-dedup.d.ts +1 -1
  120. package/plugin/dist/service/evolution-worker.d.ts +7 -31
  121. package/plugin/dist/service/evolution-worker.js +17 -36
  122. package/plugin/dist/service/queue-io.js +14 -2
  123. package/plugin/dist/service/queue-migration.d.ts +27 -26
  124. package/plugin/dist/service/queue-migration.js +57 -0
  125. package/plugin/templates/langs/en/core/AGENTS.md +2 -2
  126. package/plugin/templates/langs/en/core/TOOLS.md +1 -1
  127. package/plugin/templates/langs/zh/core/AGENTS.md +2 -2
  128. package/plugin/templates/langs/zh/core/TOOLS.md +1 -1
  129. package/templates/langs/en/core/AGENTS.md +2 -2
  130. package/templates/langs/en/core/TOOLS.md +1 -1
  131. package/templates/langs/zh/core/AGENTS.md +2 -2
  132. package/templates/langs/zh/core/TOOLS.md +1 -1
  133. package/plugin/dist/constants/diagnostician.d.ts +0 -12
  134. package/plugin/dist/constants/diagnostician.js +0 -65
  135. package/plugin/dist/service/evolution-queue-migration.d.ts +0 -128
  136. package/plugin/dist/service/evolution-queue-migration.js +0 -53
  137. package/plugin/templates/langs/en/skills/pd-diagnostician/SKILL.md +0 -466
  138. package/plugin/templates/langs/zh/skills/pd-diagnostician/SKILL.md +0 -391
  139. 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
- /** 状态点样式:ready / notready / off */
8
- function statusDotClasses(agent) {
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 (!agent.enabled) {
15
+ if (!enabled) {
11
16
  return cn(base, "border-ink-4 bg-surface opacity-50");
12
17
  }
13
- switch (agent.readiness) {
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))) })] }), 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") })] })] }))] }))] }));
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.id), 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.id}`, children: [_jsx(Trash2, { className: "w-4 h-4 text-danger/80", "aria-hidden": "true" }), t("pages.debt.actionDeactivate"), " (", ar.channel, ")"] }, ar.id)))) : (_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"
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.id !== activationId);
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
- id: string;
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
- generatedAt: string;
203
- note?: string;
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, 'id') || !isString(v.id))
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
- id: v.id, artifactId: v.artifactId, principleId: v.principleId,
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, 'generatedAt') || !isString(v.generatedAt))
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, generatedAt: v.generatedAt };
595
- if (Object.hasOwn(v, 'note') && isString(v.note))
596
- result.note = v.note;
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) {