create-principles-disciple 1.145.25 → 1.145.27

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 (191) hide show
  1. package/_release/product-identity.json +1 -1
  2. package/console/dist/server/config/pd-config-store.d.ts +1 -0
  3. package/console/dist/server/config/pd-config-store.js +28 -6
  4. package/console/dist/server/models/RuleCandidateBindingConsoleModel.d.ts +26 -0
  5. package/console/dist/server/models/RuleCandidateBindingConsoleModel.js +309 -19
  6. package/console/dist/server/routes/config.js +7 -3
  7. package/console/dist/server/routes/rule-candidate-bindings.js +26 -1
  8. package/console/dist/shared/rule-candidate-binding-contract.d.ts +93 -8
  9. package/console/dist/shared/rule-candidate-binding-contract.js +4 -0
  10. package/console/dist/ui/api.d.ts +28 -0
  11. package/console/dist/ui/api.js +13 -1
  12. package/console/dist/ui/i18n/en.json +93 -25
  13. package/console/dist/ui/i18n/zh-CN.json +79 -11
  14. package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +32 -67
  15. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +29 -0
  16. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +229 -3
  17. package/console/dist/ui/utils/rule-binding-validators.d.ts +5 -0
  18. package/console/dist/ui/utils/rule-binding-validators.js +149 -7
  19. package/console/dist/web/assets/app.css +6 -0
  20. package/console/dist/web/assets/app.js +32 -32
  21. package/core/dist/runtime-v2/adapter/index.d.ts +0 -2
  22. package/core/dist/runtime-v2/adapter/index.d.ts.map +1 -1
  23. package/core/dist/runtime-v2/adapter/index.js +0 -1
  24. package/core/dist/runtime-v2/adapter/index.js.map +1 -1
  25. package/core/dist/runtime-v2/adapter/output-schema-registry.d.ts +3 -3
  26. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
  27. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +1 -2
  28. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  29. package/core/dist/runtime-v2/cli/probe.d.ts +4 -15
  30. package/core/dist/runtime-v2/cli/probe.d.ts.map +1 -1
  31. package/core/dist/runtime-v2/cli/probe.js +24 -44
  32. package/core/dist/runtime-v2/cli/probe.js.map +1 -1
  33. package/core/dist/runtime-v2/config/index.d.ts +1 -1
  34. package/core/dist/runtime-v2/config/index.d.ts.map +1 -1
  35. package/core/dist/runtime-v2/config/index.js.map +1 -1
  36. package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts +10 -17
  37. package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts.map +1 -1
  38. package/core/dist/runtime-v2/config/pd-config-agent-binding.js +23 -27
  39. package/core/dist/runtime-v2/config/pd-config-agent-binding.js.map +1 -1
  40. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +3 -5
  41. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
  42. package/core/dist/runtime-v2/config/pd-config-defaults.js +17 -20
  43. package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
  44. package/core/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -1
  45. package/core/dist/runtime-v2/config/pd-config-effective.js +4 -1
  46. package/core/dist/runtime-v2/config/pd-config-effective.js.map +1 -1
  47. package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
  48. package/core/dist/runtime-v2/config/pd-config-redaction.js +4 -7
  49. package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
  50. package/core/dist/runtime-v2/config/pd-config-types.d.ts +5 -1
  51. package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
  52. package/core/dist/runtime-v2/config/pd-config-types.js +6 -0
  53. package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
  54. package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
  55. package/core/dist/runtime-v2/config/pd-config-validate.js +6 -0
  56. package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
  57. package/core/dist/runtime-v2/golden-trace.d.ts.map +1 -1
  58. package/core/dist/runtime-v2/golden-trace.js +8 -1
  59. package/core/dist/runtime-v2/golden-trace.js.map +1 -1
  60. package/core/dist/runtime-v2/index.d.ts +4 -4
  61. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  62. package/core/dist/runtime-v2/index.js +2 -3
  63. package/core/dist/runtime-v2/index.js.map +1 -1
  64. package/core/dist/runtime-v2/internalization/artificer-intent-preflight.d.ts.map +1 -1
  65. package/core/dist/runtime-v2/internalization/artificer-intent-preflight.js +26 -95
  66. package/core/dist/runtime-v2/internalization/artificer-intent-preflight.js.map +1 -1
  67. package/core/dist/runtime-v2/internalization/bash-risk.d.ts +32 -0
  68. package/core/dist/runtime-v2/internalization/bash-risk.d.ts.map +1 -0
  69. package/core/dist/runtime-v2/internalization/bash-risk.js +785 -0
  70. package/core/dist/runtime-v2/internalization/bash-risk.js.map +1 -0
  71. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +25 -2
  72. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
  73. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js +177 -14
  74. package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
  75. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  76. package/core/dist/runtime-v2/internalization/evaluator-runner.js +14 -2
  77. package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  78. package/core/dist/runtime-v2/internalization/internalization-orchestrator.d.ts +47 -0
  79. package/core/dist/runtime-v2/internalization/internalization-orchestrator.d.ts.map +1 -1
  80. package/core/dist/runtime-v2/internalization/internalization-orchestrator.js +164 -3
  81. package/core/dist/runtime-v2/internalization/internalization-orchestrator.js.map +1 -1
  82. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +9 -1
  83. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  84. package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  85. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts +39 -0
  86. package/core/dist/runtime-v2/internalization/pitask-metadata.d.ts.map +1 -1
  87. package/core/dist/runtime-v2/internalization/pitask-metadata.js +59 -0
  88. package/core/dist/runtime-v2/internalization/pitask-metadata.js.map +1 -1
  89. package/core/dist/runtime-v2/internalization/rule-source-correction.d.ts +60 -0
  90. package/core/dist/runtime-v2/internalization/rule-source-correction.d.ts.map +1 -0
  91. package/core/dist/runtime-v2/internalization/rule-source-correction.js +214 -0
  92. package/core/dist/runtime-v2/internalization/rule-source-correction.js.map +1 -0
  93. package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.d.ts.map +1 -1
  94. package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js +65 -0
  95. package/core/dist/runtime-v2/internalization/rulecode-verification-obligations.js.map +1 -1
  96. package/core/dist/runtime-v2/internalization/scribe-output.d.ts +14 -2
  97. package/core/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
  98. package/core/dist/runtime-v2/internalization/scribe-output.js +57 -2
  99. package/core/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
  100. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts +13 -1
  101. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.d.ts.map +1 -1
  102. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js +15 -3
  103. package/core/dist/runtime-v2/internalization/scribe-prompt-builder.js.map +1 -1
  104. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +34 -0
  105. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
  106. package/core/dist/runtime-v2/internalization/scribe-runner.js +128 -4
  107. package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
  108. package/core/dist/runtime-v2/internalization/textual-basis-anchor.d.ts +72 -0
  109. package/core/dist/runtime-v2/internalization/textual-basis-anchor.d.ts.map +1 -0
  110. package/core/dist/runtime-v2/internalization/textual-basis-anchor.js +100 -0
  111. package/core/dist/runtime-v2/internalization/textual-basis-anchor.js.map +1 -0
  112. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +9 -7
  113. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  114. package/core/dist/runtime-v2/pain-signal-runtime-factory.js +59 -121
  115. package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  116. package/core/dist/runtime-v2/runtime-protocol.d.ts +2 -3
  117. package/core/dist/runtime-v2/runtime-protocol.d.ts.map +1 -1
  118. package/core/dist/runtime-v2/runtime-protocol.js +15 -10
  119. package/core/dist/runtime-v2/runtime-protocol.js.map +1 -1
  120. package/core/dist/runtime-v2/store/lifecycle/lease-manager.d.ts.map +1 -1
  121. package/core/dist/runtime-v2/store/lifecycle/lease-manager.js +11 -2
  122. package/core/dist/runtime-v2/store/lifecycle/lease-manager.js.map +1 -1
  123. package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.d.ts.map +1 -1
  124. package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.js +26 -0
  125. package/core/dist/runtime-v2/tools/artificer-l2-tool-contract.js.map +1 -1
  126. package/core/dist/telemetry-event.d.ts +2 -2
  127. package/core/dist/telemetry-event.d.ts.map +1 -1
  128. package/core/dist/telemetry-event.js +5 -0
  129. package/core/dist/telemetry-event.js.map +1 -1
  130. package/core/dist/workflow-funnel-loader.d.ts +0 -2
  131. package/core/dist/workflow-funnel-loader.d.ts.map +1 -1
  132. package/core/dist/workflow-funnel-loader.js.map +1 -1
  133. package/core/package.json +5 -1
  134. package/dist/mvp-config.d.ts.map +1 -1
  135. package/dist/mvp-config.js +8 -10
  136. package/dist/mvp-config.js.map +1 -1
  137. package/host-runtime/dist/internalization-consumer-cycle.js +4 -7
  138. package/host-runtime/dist/production-rulehost-gate.js +11 -1
  139. package/host-runtime/package.json +1 -1
  140. package/package.json +2 -2
  141. package/pd-cli/dist/commands/diagnose.d.ts +0 -3
  142. package/pd-cli/dist/commands/diagnose.d.ts.map +1 -1
  143. package/pd-cli/dist/commands/diagnose.js +20 -57
  144. package/pd-cli/dist/commands/diagnose.js.map +1 -1
  145. package/pd-cli/dist/commands/pain-retry.d.ts +0 -3
  146. package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
  147. package/pd-cli/dist/commands/pain-retry.js +11 -39
  148. package/pd-cli/dist/commands/pain-retry.js.map +1 -1
  149. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
  150. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js +32 -0
  151. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
  152. package/pd-cli/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  153. package/pd-cli/dist/commands/runtime-internalization-run-once.js +4 -2
  154. package/pd-cli/dist/commands/runtime-internalization-run-once.js.map +1 -1
  155. package/pd-cli/dist/commands/runtime.d.ts +1 -4
  156. package/pd-cli/dist/commands/runtime.d.ts.map +1 -1
  157. package/pd-cli/dist/commands/runtime.js +25 -95
  158. package/pd-cli/dist/commands/runtime.js.map +1 -1
  159. package/pd-cli/dist/index.js +2 -5
  160. package/pd-cli/dist/index.js.map +1 -1
  161. package/pd-cli/dist/services/config-doctor.d.ts.map +1 -1
  162. package/pd-cli/dist/services/config-doctor.js +9 -21
  163. package/pd-cli/dist/services/config-doctor.js.map +1 -1
  164. package/pd-cli/dist/services/mainline-snapshot-assembler.d.ts.map +1 -1
  165. package/pd-cli/dist/services/mainline-snapshot-assembler.js +2 -2
  166. package/pd-cli/dist/services/mainline-snapshot-assembler.js.map +1 -1
  167. package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts +0 -7
  168. package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts.map +1 -1
  169. package/pd-cli/dist/services/resolve-runtime-from-pd-config.js +0 -5
  170. package/pd-cli/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
  171. package/pd-cli/dist/services/runtime-adapter-resolver.d.ts +1 -8
  172. package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  173. package/pd-cli/dist/services/runtime-adapter-resolver.js +13 -39
  174. package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
  175. package/pd-cli/package.json +1 -1
  176. package/plugin/dist/bundle.js +468 -458
  177. package/plugin/dist/openclaw.plugin.json +1 -1
  178. package/plugin/openclaw.plugin.json +1 -1
  179. package/plugin/package.json +1 -1
  180. package/release-manager/dist/mvp-config.d.ts.map +1 -1
  181. package/release-manager/dist/mvp-config.js +8 -10
  182. package/release-manager/dist/mvp-config.js.map +1 -1
  183. package/release-manager/package.json +2 -2
  184. package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.d.ts +0 -72
  185. package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.d.ts.map +0 -1
  186. package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js +0 -837
  187. package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js.map +0 -1
  188. package/core/dist/runtime-v2/utils/cli-process-runner.d.ts +0 -35
  189. package/core/dist/runtime-v2/utils/cli-process-runner.d.ts.map +0 -1
  190. package/core/dist/runtime-v2/utils/cli-process-runner.js +0 -263
  191. package/core/dist/runtime-v2/utils/cli-process-runner.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { toast } from "sonner";
@@ -7,12 +7,10 @@ import { enumLabel } from "../../utils/enum-labels.js";
7
7
  import { createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, } from "../../api.js";
8
8
  const EMPTY_FORM = {
9
9
  id: "",
10
- type: "openclaw",
11
10
  provider: "",
12
11
  model: "",
13
12
  apiKeyEnv: "",
14
13
  baseUrl: "",
15
- source: "",
16
14
  };
17
15
  /** 状态点样式:与 AgentCard 一致 (ready=green, needs_setup/not_ready/unknown=amber, disabled=ink-4) */
18
16
  function readinessDotClasses(readiness) {
@@ -32,69 +30,45 @@ function readinessDotClasses(readiness) {
32
30
  }
33
31
  /**
34
32
  * Build the profile object for a create request.
35
- * Only includes fields relevant to the chosen type; omits empty optionals.
33
+ * Always pi-ai (ADR-0029); empty optional fields are omitted.
36
34
  */
37
35
  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();
36
+ const profile = {
37
+ type: "pi-ai",
38
+ provider: form.provider.trim(),
39
+ model: form.model.trim(),
40
+ apiKeyEnv: form.apiKeyEnv.trim(),
41
+ };
42
+ if (form.baseUrl.trim() !== "") {
43
+ profile.baseUrl = form.baseUrl.trim();
54
44
  }
55
45
  return profile;
56
46
  }
57
47
  /**
58
48
  * 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).
49
+ * Sends only pi-ai fields. Empty optionals are omitted so existing values are
50
+ * preserved (merge-patch semantics on the server).
62
51
  */
63
52
  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
- }
53
+ const patch = {};
54
+ if (form.provider.trim() !== "")
55
+ patch.provider = form.provider.trim();
56
+ if (form.model.trim() !== "")
57
+ patch.model = form.model.trim();
58
+ if (form.apiKeyEnv.trim() !== "")
59
+ patch.apiKeyEnv = form.apiKeyEnv.trim();
60
+ if (form.baseUrl.trim() !== "")
61
+ patch.baseUrl = form.baseUrl.trim();
83
62
  return patch;
84
63
  }
85
- /** Pre-fill form from an existing profile summary (edit mode). */
64
+ /** Pre-fill form from an existing pi-ai profile summary (edit mode). */
86
65
  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
66
  return {
91
67
  id: p.id,
92
- type,
93
68
  provider: p.provider ?? "",
94
69
  model: p.model ?? "",
95
70
  apiKeyEnv: p.apiKeyEnv ?? "",
96
71
  baseUrl: "",
97
- source: "",
98
72
  };
99
73
  }
100
74
  export function RuntimeProfileManager({ profiles, defaultRuntimeId, onMutated, }) {
@@ -129,16 +103,14 @@ export function RuntimeProfileManager({ profiles, defaultRuntimeId, onMutated, }
129
103
  if (mode === "create" && form.id.trim() === "") {
130
104
  return t("pages.controlCenter.profiles.validationIdRequired");
131
105
  }
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
- }
106
+ if (form.provider.trim() === "") {
107
+ return t("pages.controlCenter.profiles.validationProviderRequired");
108
+ }
109
+ if (form.model.trim() === "") {
110
+ return t("pages.controlCenter.profiles.validationModelRequired");
111
+ }
112
+ if (form.apiKeyEnv.trim() === "") {
113
+ return t("pages.controlCenter.profiles.validationApiKeyEnvRequired");
142
114
  }
143
115
  return null;
144
116
  }
@@ -206,21 +178,14 @@ export function RuntimeProfileManager({ profiles, defaultRuntimeId, onMutated, }
206
178
  ? t("pages.controlCenter.profiles.formTitleCreate")
207
179
  : t("pages.controlCenter.profiles.formTitleEdit", {
208
180
  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-danger/35 rounded-[4px] bg-danger/10 text-[12px] text-danger 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
181
+ }) }), _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-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 })] })] }), formError !== null && (_jsx("div", { className: "mt-3 px-[12px] py-[8px] border border-danger/35 rounded-[4px] bg-danger/10 text-[12px] text-danger 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
182
  ? mode === "create"
218
183
  ? t("pages.controlCenter.profiles.creating")
219
184
  : t("pages.controlCenter.profiles.saving")
220
185
  : 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
186
  const isDefault = p.id === defaultRuntimeId;
222
187
  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: () => {
188
+ 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" }))] }), _jsxs("span", { className: "font-mono text-[11.5px] text-ink-3 flex items-center gap-[5px]", children: [p.type, p.type === "openclaw" && (_jsx("span", { className: "text-[9.5px] font-mono px-[5px] py-[1px] rounded-[2px] bg-amber/10 text-amber border border-amber/22 tracking-[0.02em]", children: t("pages.controlCenter.profiles.retiredBadge") }))] }), _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: [p.type !== "openclaw" && (_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
189
  setDeleteTarget(p.id);
225
190
  setDeleteError(null);
226
191
  }, disabled: mode !== "closed" ||
@@ -1,2 +1,31 @@
1
+ import type { RuleBindingQueueItem } from '../../../shared/rule-candidate-binding-contract.js';
2
+ export type RuleBindingCardState = 'awaiting_owner' | 'bound_generating' | 'bound_blocked' | 'bound_advanced' | 'bound_not_built' | 'bound_ambiguous' | 'revoked' | 'attention';
3
+ /** ERR-106: exhaustive display map over the card state union — no default branch. */
4
+ export declare const RULE_BINDING_CARD_STATE_KEY: Record<RuleBindingCardState, string>;
5
+ export declare const RULE_BINDING_CARD_STATE_CLASS: Record<RuleBindingCardState, string>;
6
+ export declare function deriveRuleBindingCardState(item: RuleBindingQueueItem): RuleBindingCardState;
7
+ export interface RuleBindingPainGroup {
8
+ key: string;
9
+ pain: RuleBindingQueueItem['pain'];
10
+ items: RuleBindingQueueItem[];
11
+ }
12
+ /** Group by canonical pain id; a missing pain id gets its own per-candidate
13
+ * group so unattributable candidates never vanish into someone else's group. */
14
+ export declare function groupRuleBindingQueue(items: RuleBindingQueueItem[]): RuleBindingPainGroup[];
15
+ /** The resume action may only appear when the real backend says ready AND the
16
+ * chain is not currently running — a revision during an active run would be
17
+ * a fake retry, not the bounded source revision. */
18
+ export declare function canOfferRuleBindingResume(item: RuleBindingQueueItem): boolean;
19
+ /**
20
+ * PRI-1048 R6 #4: the source-correction entry applies when the bounded
21
+ * source-correction budget is available on a bound+blocked chain — the shape
22
+ * where the binding revision budget is typically already spent (the 52d
23
+ * production candidate). Real eligibility is resolved server-side
24
+ * (previewRuleSourceCorrection); click-time authority is reviseRuleSourceOnce.
25
+ */
26
+ export declare function canOfferSourceCorrection(item: Pick<RuleBindingQueueItem, 'binding' | 'generation' | 'sourceCorrection'>): boolean;
27
+ /** Model-synthesized fallback sources must stay localized (CodeRabbit PRI-1048:
28
+ * `unknown` previously fell through to raw English in the zh-CN group header). */
29
+ export declare const PAIN_SOURCE_KEY: Record<string, string>;
1
30
  /** Inline in Governance Focus; assignment, rule approval and live promotion remain separate. */
2
31
  export declare function RuleCandidateBindingPanel(): import("react").JSX.Element | null;
@@ -2,18 +2,155 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { useEffect, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Link } from 'react-router-dom';
5
- import { fetchRuleBindingQueue, fetchRuleBindingReview, mutateRuleBinding } from '../../api.js';
5
+ import { fetchRuleBindingQueue, fetchRuleBindingReview, mutateRuleBinding, reviseRuleBindingOnce, correctRuleSource } from '../../api.js';
6
+ import { splitReadingTitleBody } from '../../utils/reading-layout.js';
7
+ /**
8
+ * PRI-1048 治理焦点「规则的正式原则归属」队列。
9
+ * 按痛苦信号分组的有界卡片列表;每张卡默认回答三问:
10
+ * 这是哪次反馈 / 现在卡在哪 / 我要做什么。技术 ID 与原始 JSON 只进「技术详情」。
11
+ * 归属、规则审批、激活三权分立不变(文案见 separate/declaration)。
12
+ */
13
+ /** Collapsed first screen shows at most this many pain groups. */
14
+ const COLLAPSED_GROUP_LIMIT = 3;
15
+ /** ERR-106: exhaustive display map over the card state union — no default branch. */
16
+ export const RULE_BINDING_CARD_STATE_KEY = {
17
+ awaiting_owner: 'pages.focus.ruleBinding.stateAwaitingOwner',
18
+ bound_generating: 'pages.focus.ruleBinding.stateBoundGenerating',
19
+ bound_blocked: 'pages.focus.ruleBinding.stateBoundBlocked',
20
+ bound_advanced: 'pages.focus.ruleBinding.stateBoundAdvanced',
21
+ bound_not_built: 'pages.focus.ruleBinding.stateBoundNotBuilt',
22
+ bound_ambiguous: 'pages.focus.ruleBinding.stateBoundAmbiguous',
23
+ revoked: 'pages.focus.ruleBinding.stateRevoked',
24
+ attention: 'pages.focus.ruleBinding.stateAttention',
25
+ };
26
+ export const RULE_BINDING_CARD_STATE_CLASS = {
27
+ awaiting_owner: 'border-gov/40 text-gov',
28
+ bound_generating: 'border-green/40 text-green',
29
+ bound_blocked: 'border-amber/45 text-amber',
30
+ bound_advanced: 'border-green/40 text-green',
31
+ bound_not_built: 'border-line text-ink-3',
32
+ bound_ambiguous: 'border-amber/45 text-amber',
33
+ revoked: 'border-line text-ink-3',
34
+ attention: 'border-amber/45 text-amber',
35
+ };
36
+ export function deriveRuleBindingCardState(item) {
37
+ switch (item.binding.status) {
38
+ case 'unlinked': return 'awaiting_owner';
39
+ case 'binding_revoked': return 'revoked';
40
+ case 'attention': return 'attention';
41
+ case 'bound':
42
+ switch (item.generation?.stage) {
43
+ case null:
44
+ case undefined:
45
+ case 'not_built': return 'bound_not_built';
46
+ case 'in_progress': return 'bound_generating';
47
+ case 'blocked': return 'bound_blocked';
48
+ case 'advanced': return 'bound_advanced';
49
+ case 'ambiguous': return 'bound_ambiguous';
50
+ }
51
+ }
52
+ }
53
+ /** Group by canonical pain id; a missing pain id gets its own per-candidate
54
+ * group so unattributable candidates never vanish into someone else's group. */
55
+ export function groupRuleBindingQueue(items) {
56
+ const groups = new Map();
57
+ for (const item of items) {
58
+ const key = item.pain.painId === '' ? `candidate:${item.candidateId}` : item.pain.painId;
59
+ const group = groups.get(key) ?? { key, pain: item.pain, items: [] };
60
+ group.items.push(item);
61
+ groups.set(key, group);
62
+ }
63
+ return [...groups.values()];
64
+ }
65
+ /** The resume action may only appear when the real backend says ready AND the
66
+ * chain is not currently running — a revision during an active run would be
67
+ * a fake retry, not the bounded source revision. */
68
+ export function canOfferRuleBindingResume(item) {
69
+ return item.binding.status === 'bound'
70
+ && item.resume?.status === 'ready'
71
+ && item.generation?.stage !== 'in_progress'
72
+ && item.generation?.stage !== 'ambiguous';
73
+ }
74
+ /**
75
+ * PRI-1048 R6 #4: the source-correction entry applies when the bounded
76
+ * source-correction budget is available on a bound+blocked chain — the shape
77
+ * where the binding revision budget is typically already spent (the 52d
78
+ * production candidate). Real eligibility is resolved server-side
79
+ * (previewRuleSourceCorrection); click-time authority is reviseRuleSourceOnce.
80
+ */
81
+ export function canOfferSourceCorrection(item) {
82
+ return item.binding.status === 'bound'
83
+ && item.generation?.stage === 'blocked'
84
+ && item.sourceCorrection?.status === 'ready';
85
+ }
86
+ /** ERR-106: next-step copy and the left bar also use exhaustive maps. */
87
+ const RULE_BINDING_NEXT_STEP_KEY = {
88
+ awaiting_owner: 'pages.focus.ruleBinding.nextAwaitingOwner',
89
+ bound_generating: 'pages.focus.ruleBinding.nextBoundGenerating',
90
+ bound_blocked: 'pages.focus.ruleBinding.nextBoundBlocked',
91
+ bound_advanced: 'pages.focus.ruleBinding.nextBoundAdvanced',
92
+ bound_not_built: 'pages.focus.ruleBinding.nextBoundNotBuilt',
93
+ bound_ambiguous: 'pages.focus.ruleBinding.nextBoundAmbiguous',
94
+ revoked: 'pages.focus.ruleBinding.nextRevoked',
95
+ attention: 'pages.focus.ruleBinding.nextAttention',
96
+ };
97
+ const RULE_BINDING_CARD_STATE_BAR = {
98
+ awaiting_owner: 'bg-gov',
99
+ bound_generating: 'bg-green',
100
+ bound_blocked: 'bg-amber',
101
+ bound_advanced: 'bg-green',
102
+ bound_not_built: 'bg-line-2',
103
+ bound_ambiguous: 'bg-amber',
104
+ revoked: 'bg-line-2',
105
+ attention: 'bg-amber',
106
+ };
107
+ /** Model-synthesized fallback sources must stay localized (CodeRabbit PRI-1048:
108
+ * `unknown` previously fell through to raw English in the zh-CN group header). */
109
+ export const PAIN_SOURCE_KEY = {
110
+ unreadable: 'pages.focus.ruleBinding.sourceUnreadable',
111
+ unknown: 'pages.focus.ruleBinding.sourceUnknown',
112
+ manual: 'pages.focus.ruleBinding.sourceManual',
113
+ tool_failure: 'pages.focus.ruleBinding.sourceToolFailure',
114
+ };
115
+ const STAGE_KIND_KEY = {
116
+ dreamer: 'pages.focus.ruleBinding.stageDreamer',
117
+ philosopher: 'pages.focus.ruleBinding.stagePhilosopher',
118
+ scribe: 'pages.focus.ruleBinding.stageScribe',
119
+ artificer: 'pages.focus.ruleBinding.stageArtificer',
120
+ evaluator: 'pages.focus.ruleBinding.stageEvaluator',
121
+ rollout_reviewer: 'pages.focus.ruleBinding.stageRolloutReviewer',
122
+ };
123
+ /** i18n with graceful fallback to the raw server code (visible, not dropped). */
124
+ function codeText(t, prefix, code) {
125
+ if (!code)
126
+ return '';
127
+ const key = `${prefix}.${code}`;
128
+ const text = t(key);
129
+ return text === key ? code : text;
130
+ }
131
+ function painSourceLabel(t, source) {
132
+ const key = PAIN_SOURCE_KEY[source];
133
+ return key ? t(key) : source;
134
+ }
135
+ function stageKindLabel(t, stageKind) {
136
+ if (!stageKind)
137
+ return null;
138
+ const key = STAGE_KIND_KEY[stageKind];
139
+ return key ? t(key) : stageKind;
140
+ }
6
141
  /** Inline in Governance Focus; assignment, rule approval and live promotion remain separate. */
7
142
  export function RuleCandidateBindingPanel() {
8
143
  const { t } = useTranslation();
9
144
  const [queue, setQueue] = useState(null);
10
145
  const [review, setReview] = useState(null);
146
+ const [openCandidateId, setOpenCandidateId] = useState('');
11
147
  const [search, setSearch] = useState('');
12
148
  const [selectedId, setSelectedId] = useState('');
13
149
  const [reason, setReason] = useState('');
14
150
  const [busy, setBusy] = useState(false);
15
151
  const [message, setMessage] = useState('');
16
152
  const [error, setError] = useState('');
153
+ const [showAllGroups, setShowAllGroups] = useState(false);
17
154
  useEffect(() => {
18
155
  let active = true;
19
156
  void fetchRuleBindingQueue().then(result => {
@@ -32,12 +169,19 @@ export function RuleCandidateBindingPanel() {
32
169
  const result = await fetchRuleBindingReview(candidateId, query);
33
170
  if (result.success) {
34
171
  setReview(result.data);
172
+ setOpenCandidateId(candidateId);
35
173
  setSelectedId('');
36
174
  }
37
175
  else
38
176
  setError([result.error, result.nextAction].filter(Boolean).join(' ') || t('pages.focus.ruleBinding.unavailable'));
39
177
  setBusy(false);
40
178
  }
179
+ function closeReview() { setOpenCandidateId(''); setReview(null); setSearch(''); setReason(''); }
180
+ async function refreshQueue() {
181
+ const result = await fetchRuleBindingQueue();
182
+ if (result.success)
183
+ setQueue(result.data);
184
+ }
41
185
  async function decide(action) {
42
186
  if (!review)
43
187
  return;
@@ -56,13 +200,95 @@ export function RuleCandidateBindingPanel() {
56
200
  setMessage(action === 'revoke' ? t('pages.focus.ruleBinding.revoked')
57
201
  : result.data.revisionStatus === 'refused' ? t('pages.focus.ruleBinding.resumeFailed') : t('pages.focus.ruleBinding.bound'));
58
202
  await load(review.candidateId, search);
203
+ await refreshQueue();
204
+ }
205
+ else
206
+ setError([result.error, result.nextAction].filter(Boolean).join(' ') || t('pages.focus.ruleBinding.unavailable'));
207
+ setBusy(false);
208
+ }
209
+ /** Only reachable when the real resolver said ready (canOfferRuleBindingResume). */
210
+ async function revise() {
211
+ if (!review?.resume || review.resume.status !== 'ready' || review.resume.scribeTaskId === null || review.resume.taskRevisionEpoch === null)
212
+ return;
213
+ setBusy(true);
214
+ setError('');
215
+ setMessage('');
216
+ const result = await reviseRuleBindingOnce(review.candidateId, {
217
+ expectedBindingRevision: review.currentRevision,
218
+ expectedScribeTaskId: review.resume.scribeTaskId,
219
+ expectedTaskRevisionEpoch: review.resume.taskRevisionEpoch,
220
+ requestId: crypto.randomUUID(),
221
+ });
222
+ if (result.success) {
223
+ setMessage(result.data.status === 'revision_already_started'
224
+ ? t('pages.focus.ruleBinding.reviseAlreadyStarted')
225
+ : t('pages.focus.ruleBinding.reviseStarted'));
226
+ await load(review.candidateId, search);
227
+ await refreshQueue();
228
+ }
229
+ else
230
+ setError([result.error, result.nextAction].filter(Boolean).join(' ') || t('pages.focus.ruleBinding.unavailable'));
231
+ setBusy(false);
232
+ }
233
+ /** PRI-1048 R6 #4: only reachable when the server preview said ready
234
+ * (canOfferSourceCorrection); the server re-validates every CAS input. */
235
+ async function correctSource() {
236
+ const preview = review?.sourceCorrection;
237
+ if (!review || !preview || preview.status !== 'ready'
238
+ || preview.scribeTaskId === null || preview.artificerTaskId === null
239
+ || preview.scribeArtifactDigest === null || preview.expectedBindingRevision === null)
240
+ return;
241
+ setBusy(true);
242
+ setError('');
243
+ setMessage('');
244
+ const result = await correctRuleSource(review.candidateId, {
245
+ expectedBindingRevision: preview.expectedBindingRevision,
246
+ expectedScribeTaskId: preview.scribeTaskId,
247
+ expectedArtificerTaskId: preview.artificerTaskId,
248
+ expectedScribeArtifactDigest: preview.scribeArtifactDigest,
249
+ reason,
250
+ requestId: crypto.randomUUID(),
251
+ });
252
+ if (result.success) {
253
+ setMessage(result.data.status === 'revision_already_started'
254
+ ? t('pages.focus.ruleBinding.sourceCorrectionAlreadyStarted')
255
+ : t('pages.focus.ruleBinding.sourceCorrectionStarted'));
256
+ await load(review.candidateId, search);
257
+ await refreshQueue();
59
258
  }
60
259
  else
61
260
  setError([result.error, result.nextAction].filter(Boolean).join(' ') || t('pages.focus.ruleBinding.unavailable'));
62
261
  setBusy(false);
63
262
  }
64
- if (queue?.items.length === 0 && !error)
263
+ if ((queue === null || queue.items.length === 0) && !error)
65
264
  return null;
265
+ const groups = groupRuleBindingQueue(queue?.items ?? []);
266
+ const visibleGroups = showAllGroups ? groups : groups.slice(0, COLLAPSED_GROUP_LIMIT);
66
267
  const bound = review?.currentBinding?.action === 'bind';
67
- return _jsxs("section", { className: "mb-7 rounded border border-line p-4", "data-testid": "rule-binding-panel", "aria-label": t('pages.focus.ruleBinding.title'), children: [_jsx("h2", { className: "text-sm font-medium", children: t('pages.focus.ruleBinding.title') }), _jsx("p", { className: "mt-2 text-sm text-ink-3", children: t('pages.focus.ruleBinding.separate') }), error && _jsx("p", { role: "alert", className: "my-2 text-amber", children: error }), message && _jsx("p", { role: "status", className: "my-2", children: message }), queue?.items.map(item => _jsxs("button", { className: "mt-2 block text-left text-sm underline", disabled: busy, onClick: () => { setSearch(''); setReason(''); void load(item.candidateId); }, children: [item.reason === 'bound' ? t('pages.focus.ruleBinding.manage') : t('pages.focus.ruleBinding.awaiting'), " \u00B7 ", item.text.slice(0, 100)] }, item.candidateId)), review && _jsxs("div", { className: "mt-4 border-t border-line pt-3", children: [_jsx("h3", { className: "font-medium", children: t('pages.focus.ruleBinding.candidate') }), _jsx("p", { className: "whitespace-pre-wrap text-sm", children: review.candidateText }), _jsxs("p", { className: "mt-2 text-sm", children: [t('pages.focus.ruleBinding.pain'), "\uFF1A", review.sourcePainId] }), _jsxs("details", { className: "mt-2 text-sm", children: [_jsx("summary", { children: t('pages.focus.ruleBinding.evidence') }), _jsx("pre", { className: "whitespace-pre-wrap break-words", children: review.evidence })] }), _jsxs("p", { className: "mt-3 text-sm", children: [t('pages.focus.ruleBinding.current'), "\uFF1A", bound ? review.currentBinding?.principleId : t('pages.focus.ruleBinding.unlinked')] }), !bound && _jsxs(_Fragment, { children: [_jsxs("form", { className: "mt-3 flex gap-2", onSubmit: event => { event.preventDefault(); void load(review.candidateId, search); }, children: [_jsx("input", { value: search, onChange: event => setSearch(event.target.value), maxLength: 200, "aria-label": t('pages.focus.ruleBinding.search'), className: "border border-line p-2 text-sm" }), _jsx("button", { disabled: busy, type: "submit", children: t('pages.focus.ruleBinding.search') })] }), review.eligibleTargets.length === 0 && _jsxs("p", { className: "mt-2 text-sm", children: [t('pages.focus.ruleBinding.noTarget'), " ", _jsx(Link, { to: "/principles", className: "underline", children: t('pages.focus.ruleBinding.formation') })] }), review.eligibleTargets.map(target => _jsxs("label", { className: "my-3 block border border-line p-3 text-sm", children: [_jsx("input", { type: "radio", name: "binding-target", value: target.principleId, checked: selectedId === target.principleId, onChange: () => setSelectedId(target.principleId) }), _jsxs("span", { className: "ml-2", children: [t(`principles.status${target.status[0]?.toUpperCase()}${target.status.slice(1)}`, { defaultValue: target.status }), " \u00B7 ", t('pages.focus.ruleBinding.version'), " ", target.version] }), _jsx("p", { className: "mt-2 whitespace-pre-wrap", children: target.text })] }, target.principleId)), _jsx("p", { className: "mt-3 text-sm text-ink-3", children: review.resumeNotice }), _jsx("p", { className: "mt-2 text-sm", children: t('pages.focus.ruleBinding.declaration') })] }), _jsxs("label", { className: "mt-3 block text-sm", children: [t('pages.focus.ruleBinding.reason'), _jsx("textarea", { className: "mt-1 block w-full border border-line p-2", maxLength: 2000, value: reason, onChange: event => setReason(event.target.value) })] }), _jsx("button", { className: "mt-3 rounded border border-gov px-3 py-2 text-sm", disabled: busy || !reason.trim() || (!bound && !selectedId), onClick: () => void decide(bound ? 'revoke' : 'bind'), children: bound ? t('pages.focus.ruleBinding.revoke') : t('pages.focus.ruleBinding.bind') }), _jsxs("p", { className: "mt-3 text-sm text-ink-3", children: [t('pages.focus.ruleBinding.revokeBoundary'), " ", _jsx(Link, { to: "/activation", className: "underline", children: t('pages.focus.ruleBinding.stop') })] })] })] });
268
+ return _jsxs("section", { className: "mb-7 rounded border border-line p-4", "data-testid": "rule-binding-panel", "aria-label": t('pages.focus.ruleBinding.title'), children: [_jsx("h2", { className: "text-sm font-medium", children: t('pages.focus.ruleBinding.title') }), _jsx("p", { className: "mt-2 text-sm text-ink-3", children: t('pages.focus.ruleBinding.separate') }), queue && _jsx("p", { className: "mt-2 text-[13px] text-ink-3", role: "status", "data-testid": "rule-binding-counts", children: t('pages.focus.ruleBinding.countsLine', { candidates: queue.summary.candidates, painGroups: queue.summary.painGroups, bound: queue.summary.bound, unlinked: queue.summary.unlinked, blocked: queue.summary.blocked }) }), queue?.truncated && _jsx("p", { className: "mt-1 text-[12.5px] text-amber", children: t('pages.focus.ruleBinding.truncatedNote', { count: queue.items.length }) }), error && _jsx("p", { role: "alert", className: "my-2 text-amber", children: error }), message && _jsx("p", { role: "status", className: "my-2", children: message }), visibleGroups.map(group => _jsxs("div", { className: "mt-4", "data-testid": "rule-binding-pain-group", children: [_jsxs("div", { className: "border-l-2 border-line pl-3", children: [_jsxs("div", { className: "text-[13px] text-ink-2 leading-relaxed", children: [_jsx("span", { className: "font-medium text-ink", children: t('pages.focus.ruleBinding.groupPrefix') }), ' ', group.pain.text
269
+ ? splitReadingTitleBody(group.pain.text).title
270
+ : t('pages.focus.ruleBinding.painUnrecorded')] }), _jsxs("div", { className: "mt-0.5 text-[11.5px] text-ink-4", children: [painSourceLabel(t, group.pain.source), group.pain.createdAt ? ` · ${group.pain.createdAt.slice(0, 10)}` : '', ' · ', t('pages.focus.ruleBinding.groupCount', { count: group.items.length })] })] }), group.items.map(item => {
271
+ const cardState = deriveRuleBindingCardState(item);
272
+ const layout = splitReadingTitleBody(item.text);
273
+ const open = openCandidateId === item.candidateId;
274
+ return _jsxs("article", { className: "relative mt-3 pl-[22px] py-[14px] pr-[18px] bg-panel border border-line rounded-[6px]", "data-testid": `rule-binding-card-${item.candidateId}`, children: [_jsx("div", { className: `absolute left-0 top-0 bottom-0 w-[3px] rounded-l-[6px] ${RULE_BINDING_CARD_STATE_BAR[cardState]}` }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: `inline-flex items-center border rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase ${RULE_BINDING_CARD_STATE_CLASS[cardState]}`, children: t(RULE_BINDING_CARD_STATE_KEY[cardState]) }), (() => {
275
+ const stageLabel = stageKindLabel(t, item.generation?.stageKind ?? null);
276
+ return stageLabel
277
+ ? _jsxs("span", { className: "text-[11.5px] text-ink-4", children: [t('pages.focus.ruleBinding.currentStage'), stageLabel] })
278
+ : null;
279
+ })()] }), _jsx("div", { className: "mt-[10px] mb-1 font-semibold text-ink leading-snug", "data-testid": `rule-binding-title-${item.candidateId}`, children: layout.title }), layout.body !== null && _jsx("div", { className: "mb-2 text-[14px] leading-relaxed text-ink-2 whitespace-pre-wrap break-words", children: layout.body }), item.binding.status === 'bound' && _jsxs("div", { className: "mt-2 px-3 py-2 bg-surface/60 border-l-2 border-gov text-[13px] leading-relaxed", children: [_jsx("span", { className: "text-ink-3", children: t('pages.focus.ruleBinding.boundTo') }), ' ', _jsx("span", { className: "text-ink", children: splitReadingTitleBody(item.binding.principle.text).title }), ' · ', _jsxs("span", { className: "text-ink-4", children: [t(`principles.status${item.binding.principle.status[0]?.toUpperCase()}${item.binding.principle.status.slice(1)}`, { defaultValue: item.binding.principle.status }), " \u00B7 ", t('pages.focus.ruleBinding.version'), " ", item.binding.principle.version] }), ' · ', _jsx(Link, { to: `/principles/${item.binding.principle.principleId}`, className: "text-gov underline", children: t('pages.focus.ruleBinding.viewPrinciple') })] }), cardState === 'bound_blocked' && item.generation && _jsxs("div", { className: "mt-2 text-[12.5px] leading-relaxed text-amber", "data-testid": `rule-binding-blocked-${item.candidateId}`, children: [t('pages.focus.ruleBinding.blockedLine', {
280
+ stage: stageKindLabel(t, item.generation.stageKind) ?? '',
281
+ reason: codeText(t, 'pages.focus.ruleBinding.blockReason', item.generation.reasonCode) || t('pages.focus.ruleBinding.blockReasonUnknown'),
282
+ }), _jsx("div", { className: "mt-0.5 text-ink-3", children: t('pages.focus.ruleBinding.blockedNoOwnerApproval') }), item.generation.detail && _jsxs("details", { className: "mt-1 text-[11.5px]", children: [_jsx("summary", { className: "cursor-pointer select-none", children: t('pages.focus.ruleBinding.blockedDetail') }), _jsx("pre", { className: "mt-1 max-h-40 overflow-auto whitespace-pre-wrap break-words font-mono text-[11px]", children: item.generation.detail })] })] }), cardState === 'attention' && _jsx("div", { className: "mt-2 text-[12.5px] leading-relaxed text-amber", "data-testid": `rule-binding-attention-${item.candidateId}`, children: t('pages.focus.ruleBinding.attentionLine', { reason: item.binding.status === 'attention' ? item.binding.reason : '' }) }), _jsxs("div", { className: "mt-2 text-[12.5px] text-ink-3 leading-relaxed", "data-testid": `rule-binding-next-${item.candidateId}`, children: [_jsx("span", { className: "font-medium", children: t('pages.focus.ruleBinding.nextLabel') }), ' ', cardState === 'bound_blocked' && item.resume?.status === 'refused'
283
+ ? t('pages.focus.ruleBinding.nextBoundBlockedNoResume', { reason: codeText(t, 'pages.focus.ruleBinding.resumeReason', item.resume.reason) })
284
+ : t(RULE_BINDING_NEXT_STEP_KEY[cardState])] }), _jsxs("details", { className: "mt-2 text-[11.5px] text-ink-4", children: [_jsx("summary", { className: "cursor-pointer select-none", children: t('pages.focus.ruleBinding.techDetails') }), _jsxs("dl", { className: "mt-1 font-mono text-[11px] break-all", children: [_jsxs("div", { children: ["candidateId: ", item.candidateId] }), _jsxs("div", { children: ["painId: ", item.pain.painId || '—'] }), item.binding.status === 'bound' && _jsxs("div", { children: ["bindingRevision: ", item.binding.principle.revision, " \u00B7 decidedAt: ", item.binding.principle.decidedAt || '—'] }), item.resume && _jsxs("div", { children: ["resume: ", item.resume.status, item.resume.reason ? ` (${item.resume.reason})` : '', item.resume.scribeTaskId ? ` · ${item.resume.scribeTaskId}` : ''] })] })] }), _jsx("button", { type: "button", className: "mt-3 inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", disabled: busy, "aria-expanded": open, onClick: () => { if (open)
285
+ closeReview();
286
+ else {
287
+ setSearch('');
288
+ setReason('');
289
+ void load(item.candidateId);
290
+ } }, children: open ? t('pages.focus.ruleBinding.closeReview') : t('pages.focus.ruleBinding.openReview') }), open && review && review.candidateId === item.candidateId && _jsxs("div", { className: "mt-4 border-t border-line pt-3", children: [_jsx("h3", { className: "font-medium text-[13px]", children: t('pages.focus.ruleBinding.candidate') }), _jsx("p", { className: "mt-1 whitespace-pre-wrap text-sm", children: review.candidateText }), _jsxs("p", { className: "mt-2 text-sm", children: [t('pages.focus.ruleBinding.pain'), "\uFF1A", review.sourcePain.text ?? t('pages.focus.ruleBinding.painUnrecorded')] }), _jsxs("details", { className: "mt-2 text-sm", children: [_jsx("summary", { children: t('pages.focus.ruleBinding.evidence') }), _jsx("pre", { className: "whitespace-pre-wrap break-words", children: review.evidence })] }), bound && review.boundPrinciple && _jsxs(_Fragment, { children: [_jsxs("div", { className: "mt-3 px-3 py-2 bg-surface/60 border-l-2 border-gov text-[13px] leading-relaxed", children: [_jsx("div", { className: "whitespace-pre-wrap", children: review.boundPrinciple.text }), _jsxs("div", { className: "mt-1 text-ink-4", children: [t('pages.focus.ruleBinding.boundSince', { revision: review.boundPrinciple.revision, date: review.boundPrinciple.decidedAt.slice(0, 10) || '—' }), " \u00B7 ", _jsx(Link, { to: `/principles/${review.boundPrinciple.principleId}`, className: "text-gov underline", children: t('pages.focus.ruleBinding.viewPrinciple') })] })] }), canOfferRuleBindingResume({ ...item, resume: review.resume }) && _jsx("button", { type: "button", className: "mt-3 rounded border border-gov bg-gov text-paper px-3 py-2 text-sm disabled:opacity-50", disabled: busy, onClick: () => void revise(), children: t('pages.focus.ruleBinding.reviseCta') }), canOfferSourceCorrection({ ...item, sourceCorrection: review.sourceCorrection }) && _jsx("button", { type: "button", className: "mt-3 ml-2 rounded border border-gov bg-gov text-paper px-3 py-2 text-sm disabled:opacity-50", disabled: busy || !reason.trim(), "data-testid": "rule-binding-source-correct-cta", onClick: () => void correctSource(), children: t('pages.focus.ruleBinding.sourceCorrectionCta') }), review.resume?.status === 'refused' && _jsx("p", { className: "mt-3 text-[12.5px] text-ink-3 leading-relaxed", children: t('pages.focus.ruleBinding.resumeUnavailable', { reason: codeText(t, 'pages.focus.ruleBinding.resumeReason', review.resume.reason) }) }), review.generation?.stage === 'blocked' && review.sourceCorrection?.status === 'unavailable' && _jsx("p", { className: "mt-2 text-[12.5px] text-ink-4 leading-relaxed", children: t('pages.focus.ruleBinding.sourceCorrectionUnavailable', { reason: review.sourceCorrection.reason ?? '' }) }), review.generation?.stage === 'in_progress' && _jsx("p", { className: "mt-3 text-[12.5px] text-ink-3", children: t('pages.focus.ruleBinding.nextBoundGenerating') })] }), !bound && _jsxs(_Fragment, { children: [_jsx("p", { className: "mt-3 text-sm text-ink-3", children: t('pages.focus.ruleBinding.chooseTarget') }), _jsxs("form", { className: "mt-3 flex gap-2", onSubmit: event => { event.preventDefault(); void load(review.candidateId, search); }, children: [_jsx("input", { value: search, onChange: event => setSearch(event.target.value), maxLength: 200, "aria-label": t('pages.focus.ruleBinding.search'), className: "border border-line p-2 text-sm" }), _jsx("button", { disabled: busy, type: "submit", children: t('pages.focus.ruleBinding.search') })] }), review.eligibleTargets.length === 0 && _jsxs("p", { className: "mt-2 text-sm", children: [t('pages.focus.ruleBinding.noTarget'), " ", _jsx(Link, { to: "/principles", className: "underline", children: t('pages.focus.ruleBinding.formation') })] }), review.eligibleTargets.map(target => _jsxs("label", { className: "my-3 block border border-line p-3 text-sm", children: [_jsx("input", { type: "radio", name: "binding-target", value: target.principleId, checked: selectedId === target.principleId, onChange: () => setSelectedId(target.principleId) }), _jsxs("span", { className: "ml-2", children: [t(`principles.status${target.status[0]?.toUpperCase()}${target.status.slice(1)}`, { defaultValue: target.status }), " \u00B7 ", t('pages.focus.ruleBinding.version'), " ", target.version] }), _jsx("p", { className: "mt-2 whitespace-pre-wrap", children: target.text })] }, target.principleId)), _jsx("p", { className: "mt-3 text-sm text-ink-3", children: review.resumeNotice }), _jsx("p", { className: "mt-2 text-sm", children: t('pages.focus.ruleBinding.declaration') })] }), _jsxs("label", { className: "mt-3 block text-sm", children: [t('pages.focus.ruleBinding.reason'), _jsx("textarea", { className: "mt-1 block w-full border border-line p-2", maxLength: 2000, value: reason, onChange: event => setReason(event.target.value) })] }), _jsxs("div", { className: "mt-3 flex gap-2 flex-wrap", children: [!bound && _jsx("button", { type: "button", className: "rounded border border-gov px-3 py-2 text-sm disabled:opacity-50", disabled: busy || !reason.trim() || !selectedId, onClick: () => void decide('bind'), children: t('pages.focus.ruleBinding.bind') }), bound && _jsx("button", { type: "button", className: "rounded border border-line px-3 py-2 text-sm disabled:opacity-50", disabled: busy || !reason.trim(), onClick: () => void decide('revoke'), children: t('pages.focus.ruleBinding.revokeSecondary') })] }), _jsxs("p", { className: "mt-3 text-sm text-ink-3", children: [t('pages.focus.ruleBinding.revokeBoundary'), " ", _jsx(Link, { to: "/activation", className: "underline", children: t('pages.focus.ruleBinding.stop') })] })] })] }, item.candidateId);
291
+ })] }, group.key)), groups.length > COLLAPSED_GROUP_LIMIT && _jsx("button", { type: "button", className: "mt-3 text-[12.5px] text-gov underline", onClick: () => setShowAllGroups(value => !value), "aria-expanded": showAllGroups, "data-testid": "rule-binding-groups-toggle", children: showAllGroups
292
+ ? t('pages.focus.ruleBinding.hideRemaining')
293
+ : t('pages.focus.ruleBinding.viewRemaining', { count: groups.length - COLLAPSED_GROUP_LIMIT }) })] });
68
294
  }
@@ -6,3 +6,8 @@ export declare function validateBindingMutation(value: unknown): {
6
6
  revisionStatus?: string;
7
7
  nextAction?: string;
8
8
  } | null;
9
+ export declare function validateBindingRevise(value: unknown): {
10
+ status: string;
11
+ reason?: string;
12
+ nextAction?: string;
13
+ } | null;