dsh-decision-layer 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -24,6 +24,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
24
24
  // src/client/index.tsx
25
25
  var index_exports = {};
26
26
  __export(index_exports, {
27
+ AnalysisView: () => AnalysisView,
27
28
  ConfigForm: () => ConfigForm,
28
29
  apply: () => apply,
29
30
  inject: () => inject
@@ -51,6 +52,7 @@ var dictionaries = {
51
52
  cardGate: "\u5371\u9669\u95E8\u63A7",
52
53
  cardCheck: "\u4EA7\u51FA\u81EA\u68C0",
53
54
  cardNarrow: "\u5DE5\u5177\u6536\u7A84",
55
+ cardComplete: "\u5B8C\u6210\u6838\u5BF9",
54
56
  cardTimes: " \u6B21",
55
57
  allow: "\u653E\u884C",
56
58
  ask: "\u8BE2\u95EE",
@@ -79,6 +81,7 @@ var dictionaries = {
79
81
  logGate: "\u95E8\u63A7",
80
82
  logCheck: "\u81EA\u68C0",
81
83
  logNarrow: "\u6536\u7A84",
84
+ logComplete: "\u5B8C\u6210\u6838\u5BF9",
82
85
  logSuggested: "\u6A21\u578B\u5EFA\u8BAE",
83
86
  logEvalFailed: "\u8BC4\u4F30\u5931\u8D25",
84
87
  logScore: "\u5F97\u5206",
@@ -89,13 +92,14 @@ var dictionaries = {
89
92
  logNarrowHint: "\u6BCF\u56DE\u5408\u5F00\u59CB\u65F6\uFF0C\u628A\u8FD9\u8F6E\u4EFB\u52A1\u4E0E\u5F53\u524D\u53EF\u7528\u5DE5\u5177\u9010\u4E2A\u4EA4\u7ED9\u88C1\u51B3\u6A21\u578B\u5224\u65AD\u76F8\u5173\u6027\uFF08noul\uFF09\uFF0C\u6458\u6389\u660E\u663E\u7528\u4E0D\u4E0A\u7684\u5DE5\u5177\uFF0C\u53EA\u5728\u5F53\u524D\u5BBF\u4E3B\u5DF2\u5141\u8BB8\u7684\u96C6\u5408\u5185\u53D6\u4EA4\u96C6\u3001\u7EDD\u4E0D\u6269\u6743\uFF1B\u300C\u4EC5\u89C2\u5BDF\u300D\u53EA\u8BB0\u5F55\u5EFA\u8BAE\uFF0C\u300C\u5DF2\u751F\u6548\u300D\u624D\u771F\u6B63\u5BF9\u672C\u56DE\u5408\u9650\u5236\u5DE5\u5177\u3002",
90
93
  actionDeny: "\u5DF2\u62D2\u7EDD",
91
94
  actionAsk: "\u8F6C\u4EA4\u5BA1\u6279",
95
+ actionPass: "\u672A\u5E72\u9884",
96
+ gatePassThrough: "\u672A\u5E72\u9884\uFF08\u6309\u539F\u8DEF\u5F84\u6267\u884C\uFF09",
92
97
  checkResultLow: "\u5F97\u5206\u504F\u4F4E",
93
98
  checkResultOk: "\u901A\u8FC7",
94
99
  reasonUnavailable: "\u540E\u7AEF\u4E0D\u53EF\u8FBE",
95
100
  reasonHttpError: "\u540E\u7AEF\u8FD4\u56DE\u9519\u8BEF",
96
101
  reasonInvalidResponse: "\u54CD\u5E94\u683C\u5F0F\u4E0D\u7B26",
97
102
  reasonLowConfidence: "\u7F6E\u4FE1\u5EA6\u4E0D\u8DB3",
98
- reasonCapacity: "\u4F1A\u8BDD\u72B6\u6001\u5DF2\u6EE1",
99
103
  reasonConfig: "\u914D\u7F6E\u65E0\u6548",
100
104
  reasonTooMany: "\u5019\u9009\u8FC7\u591A",
101
105
  reasonTooManyCount: "\u5019\u9009 {count} \u4E2A\u504F\u591A",
@@ -123,6 +127,25 @@ var dictionaries = {
123
127
  featureCheckHint: "\u5173\u95ED\u540E\u4E0D\u518D\u5728\u56DE\u5408\u7ED3\u675F\u65F6\u7ED9\u6700\u7EC8\u56DE\u590D\u6253\u5206\u3002",
124
128
  featureNarrow: "\u5DE5\u5177\u6536\u7A84",
125
129
  featureNarrowHint: "\u5173\u95ED\u540E\u4E0D\u518D\u5728\u56DE\u5408\u5F00\u59CB\u65F6\u8BC4\u4F30\u5DE5\u5177\u76F8\u5173\u6027\u3002\u9632\u5FAA\u73AF\u4E3A\u786E\u5B9A\u6027\u672C\u5730\u4FDD\u62A4\uFF0C\u59CB\u7EC8\u751F\u6548\uFF0C\u4E0D\u5728\u6B64\u5F00\u5173\u8303\u56F4\u3002",
130
+ featureComplete: "\u4EFB\u52A1\u5B8C\u6210\u6838\u5BF9",
131
+ featureCompleteHint: "\u5173\u95ED\u540E\u4E0D\u518D\u5728\u56DE\u5408\u7ED3\u675F\u65F6\u9010\u9879\u6838\u5BF9\u7528\u6237\u660E\u786E\u5217\u51FA\u7684\u4EA4\u4ED8\u6761\u4EF6\u3002",
132
+ trendWarnHint: "\u672C\u4F1A\u8BDD\u6700\u8FD1\u8FDE\u7EED\u51E0\u8F6E\u4EA7\u51FA\u8D28\u91CF\u504F\u4F4E\uFF08\u9AD8\u7F6E\u4FE1\uFF09\u3002\u53EF\u80FD\u662F\u4EFB\u52A1\u96BE\u5EA6\u4E0A\u5347\u6216\u6A21\u578B\u72B6\u6001\u6CE2\u52A8\uFF0C\u5EFA\u8BAE\u5C1D\u8BD5\u8C03\u6574 prompt \u6216\u66F4\u6362\u6A21\u578B\u3002",
133
+ trendSevereHint: "\u672C\u4F1A\u8BDD\u4EA7\u51FA\u8D28\u91CF\u6301\u7EED\u504F\u4F4E\uFF08\u9AD8\u7F6E\u4FE1\uFF09\u3002\u5EFA\u8BAE\u8C03\u6574 prompt \u6216\u66F4\u6362\u6A21\u578B\uFF0C\u6216\u91CD\u5F00\u4F1A\u8BDD\u3002",
134
+ rateGroup: "\u6807\u6CE8\u8FD9\u6B21\u5224\u65AD",
135
+ rateGood: "\u5224\u5BF9\u4E86",
136
+ rateBad: "\u5224\u9519\u4E86",
137
+ rateUnsure: "\u8BF4\u4E0D\u51C6",
138
+ rateSummary: "\u5DF2\u6807\u6CE8 {rated} \u6761\uFF08\u5224\u5BF9 {good} / \u5224\u9519 {bad} / \u8BF4\u4E0D\u51C6 {unsure}\uFF09",
139
+ annotateFailed: "\u6807\u6CE8\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002",
140
+ completeSteer: "\u6709\u672A\u6EE1\u8DB3\u6761\u4EF6\u65F6\u81EA\u52A8\u8865\u4E00\u8F6E",
141
+ completeHint: "\u56DE\u5408\u7ED3\u675F\u65F6\uFF0C\u5BF9\u7528\u6237\u660E\u786E\u5217\u51FA\u7684\u591A\u4E2A\u4EA4\u4ED8\u6761\u4EF6\uFF08\u7F16\u53F7 / \u9879\u76EE\u7B26\u53F7 / \u591A\u547D\u4EE4\uFF09\u9010\u9879\u5224\u5B9A\u300C\u5DF2\u6EE1\u8DB3 / \u672A\u6EE1\u8DB3 / \u8BC1\u636E\u4E0D\u8DB3\u300D\uFF0C\u4EE5\u5DE5\u5177\u8BC1\u636E\u4E3A\u51C6\u3001\u56DE\u590D\u81EA\u79F0\u5B8C\u6210\u4E0D\u7B97\u8BC1\u636E\u3002\u5173\u95ED\uFF1A\u53EA\u628A\u7ED3\u679C\u8BB0\u8FDB\u65E5\u5FD7\u548C\u7EDF\u8BA1\uFF0C\u4E0D\u6253\u65AD\u5BF9\u8BDD\u3002\u5F00\u542F\uFF1A\u786E\u6709\u6761\u4EF6\u88AB\u5224\u300C\u672A\u6EE1\u8DB3\u300D\u4E14\u6A21\u578B\u6709\u628A\u63E1\u65F6\uFF0C\u81EA\u52A8\u4EE5\u4F60\u7684\u540D\u4E49\u8FFD\u52A0\u4E00\u6761\u63D0\u793A\u8BA9 AI \u590D\u6838\u8865\u5B8C\uFF1B\u300C\u8BC1\u636E\u4E0D\u8DB3\u300D\u4E0D\u89E6\u53D1\u8865\u5B8C\uFF0C\u540C\u4E00\u56DE\u5408\u6700\u591A\u8865\u4E00\u6B21\u3002",
142
+ completeResultOk: "\u6761\u4EF6\u9F50\u5907",
143
+ completeResultUnsatisfied: "\u6709\u672A\u6EE1\u8DB3",
144
+ completeConditions: "\u6761\u4EF6",
145
+ completeSatisfied: "\u6EE1\u8DB3",
146
+ completeUnsatisfied: "\u672A\u6EE1\u8DB3",
147
+ completeInsufficient: "\u8BC1\u636E\u4E0D\u8DB3",
148
+ completeSteered: "\u5DF2\u8865\u4E00\u8F6E",
126
149
  backend: "\u88C1\u51B3\u540E\u7AEF",
127
150
  url: "\u670D\u52A1\u5730\u5740",
128
151
  model: "\u6A21\u578B",
@@ -142,9 +165,22 @@ var dictionaries = {
142
165
  unavailable: "\u8FDE\u63A5\u4E0D\u53EF\u7528",
143
166
  error: "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002",
144
167
  loading: "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u72B6\u6001",
145
- capacity: "\u4F1A\u8BDD\u72B6\u6001\u5BB9\u91CF\u5DF2\u6EE1\uFF1A\u6B64\u4F1A\u8BDD\u81EA\u52A8\u4ECB\u5165\u5DF2\u6682\u505C\uFF1B\u64CD\u4F5C\u4ECD\u53D7\u5BBF\u4E3B\u6743\u9650\u7B56\u7565\u7EA6\u675F\u3002",
146
168
  destination: "\u88C1\u51B3\u65F6\u4F1A\u5411\u4EE5\u4E0B\u540E\u7AEF\u53D1\u9001\u5FC5\u8981\u7684\u8BC4\u4F30\u4E0A\u4E0B\u6587\uFF1A",
147
- httpWarning: "\u6B64\u5730\u5740\u4F7F\u7528\u672A\u52A0\u5BC6\u7684 HTTP\u3002\u786E\u8BA4\u540E\uFF0CAPI Key \u4E0E\u8BC4\u4F30\u4E0A\u4E0B\u6587\u5C06\u4EE5\u660E\u6587\u53D1\u9001\u81F3\u8BE5\u5730\u5740\u3002"
169
+ httpWarning: "\u6B64\u5730\u5740\u4F7F\u7528\u672A\u52A0\u5BC6\u7684 HTTP\u3002\u786E\u8BA4\u540E\uFF0CAPI Key \u4E0E\u8BC4\u4F30\u4E0A\u4E0B\u6587\u5C06\u4EE5\u660E\u6587\u53D1\u9001\u81F3\u8BE5\u5730\u5740\u3002",
170
+ analysisTab: "\u51B3\u7B56\u5206\u6790",
171
+ analysisTitle: "\u51B3\u7B56\u5206\u6790",
172
+ analysisIntro: "\u57FA\u4E8E\u672C\u4F1A\u8BDD\u5DF2\u843D\u76D8\u7684\u51B3\u7B56\u65E5\u5FD7\u505A\u53EA\u8BFB\u805A\u5408\u4E0E\u4EBA\u5DE5\u6807\u6CE8\uFF0C\u4E0D\u6539\u53D8\u4EFB\u4F55 agent \u884C\u4E3A\u3002",
173
+ analysisProfile: "\u8FC7\u7A0B\u753B\u50CF",
174
+ analysisMeanScore: "\u5E73\u5747\u5206",
175
+ analysisTrend: "\u8D28\u91CF\u8D8B\u52BF\u56DE\u6D4B",
176
+ analysisTrendWarn: "\u544A\u8B66\u89E6\u53D1",
177
+ analysisTrendSevere: "\u4E25\u91CD\u89E6\u53D1",
178
+ analysisTrendMaxRun: "\u6700\u957F\u8FDE\u7EED\u4F4E\u5206",
179
+ analysisTrendNote: "\u7528\u5F53\u524D\u9608\u503C\u628A\u672C\u4F1A\u8BDD\u7684\u81EA\u68C0\u5206\u6570\u6309\u65F6\u95F4\u91CD\u653E\uFF0C\u7EDF\u8BA1\u544A\u8B66\u672C\u4F1A\u300C\u4F1A\u300D\u89E6\u53D1\u51E0\u6B21\uFF0C\u7528\u4E8E\u6821\u51C6\u9608\u503C\uFF0C\u4E0D\u662F\u5B9E\u65F6\u4FE1\u53F7\u3002",
180
+ analysisAccuracy: "\u5224\u65AD\u51C6\u786E\u5EA6",
181
+ analysisAccuracyEmpty: "\u5C1A\u672A\u6807\u6CE8\u4EFB\u4F55\u51B3\u7B56\u3002\u5728\u4E0B\u65B9\u51B3\u7B56\u5217\u8868\u9010\u6761\u6807\u6CE8\u300C\u5224\u5BF9 / \u5224\u9519 / \u8BF4\u4E0D\u51C6\u300D\u540E\uFF0C\u8FD9\u91CC\u7ED9\u51FA\u6C47\u603B\u3002",
182
+ analysisLogNote: "\u6807\u6CE8\u5728\u6B64\u4E0E\u4ECB\u5165\u9762\u677F\u540C\u6E90\uFF1A\u4EFB\u4E00\u5904\u6807\u6CE8\u90FD\u5199\u5165\u540C\u4E00\u4EFD\u65E5\u5FD7\u3001\u6309\u51B3\u7B56 id \u5173\u8054\u3001\u53D6\u6700\u65B0\u3002",
183
+ analysisLogTruncated: "\u672C\u4F1A\u8BDD\u6682\u65E0\u53EF\u5C55\u793A\u7684\u51B3\u7B56\u8BB0\u5F55\uFF08\u5185\u5B58\u65E5\u5FD7\u53EF\u80FD\u5DF2\u6EDA\u52A8\u6E05\u7406\uFF09\u3002"
148
184
  },
149
185
  en: {
150
186
  summary: "A danger-call gate, output self-check, and loop guard driven by a pluggable adjudication model.",
@@ -165,6 +201,7 @@ var dictionaries = {
165
201
  cardGate: "Danger gate",
166
202
  cardCheck: "Output self-check",
167
203
  cardNarrow: "Tool narrowing",
204
+ cardComplete: "Task completion",
168
205
  cardTimes: "",
169
206
  allow: "Allow",
170
207
  ask: "Ask",
@@ -193,6 +230,7 @@ var dictionaries = {
193
230
  logGate: "Gate",
194
231
  logCheck: "Self-check",
195
232
  logNarrow: "Narrow",
233
+ logComplete: "Completion",
196
234
  logSuggested: "model suggested",
197
235
  logEvalFailed: "evaluation failed",
198
236
  logScore: "score",
@@ -203,13 +241,14 @@ var dictionaries = {
203
241
  logNarrowHint: "At the start of each turn, the current task and each available tool are sent to the adjudication model (a noul per tool) to judge relevance, dropping tools clearly not needed. It only intersects the host-allowed set and never expands it; observe merely logs the suggestion, enforced actually restricts the tools for this turn.",
204
242
  actionDeny: "denied",
205
243
  actionAsk: "sent to approval",
244
+ actionPass: "no intervention",
245
+ gatePassThrough: "no intervention (proceeded on its original path)",
206
246
  checkResultLow: "low score",
207
247
  checkResultOk: "passed",
208
248
  reasonUnavailable: "backend unreachable",
209
249
  reasonHttpError: "backend returned an error",
210
250
  reasonInvalidResponse: "unexpected response shape",
211
251
  reasonLowConfidence: "low confidence",
212
- reasonCapacity: "session state full",
213
252
  reasonConfig: "invalid configuration",
214
253
  reasonTooMany: "too many candidates",
215
254
  reasonTooManyCount: "{count} candidates is too many",
@@ -237,6 +276,25 @@ var dictionaries = {
237
276
  featureCheckHint: "When off, the final reply is no longer scored at the end of a turn.",
238
277
  featureNarrow: "Tool narrowing",
239
278
  featureNarrowHint: "When off, tool relevance is no longer evaluated at the start of a turn. The loop guard is a deterministic local safeguard that is always on and not covered by this switch.",
279
+ featureComplete: "Task completion check",
280
+ featureCompleteHint: "When off, the explicit deliverable conditions the user listed are no longer checked at turn end.",
281
+ trendWarnHint: "Output quality has been low for a few consecutive turns this session (high confidence). It may be rising task difficulty or model drift \u2014 consider adjusting the prompt or switching models.",
282
+ trendSevereHint: "Output quality has stayed low this session (high confidence). Consider adjusting the prompt, switching models, or starting a new session.",
283
+ rateGroup: "Rate this decision",
284
+ rateGood: "Correct",
285
+ rateBad: "Wrong",
286
+ rateUnsure: "Unsure",
287
+ rateSummary: "Rated {rated} ({good} correct / {bad} wrong / {unsure} unsure)",
288
+ annotateFailed: "Could not save the rating. Please retry.",
289
+ completeSteer: "Auto re-prompt on an unmet condition",
290
+ completeHint: "At turn end, each explicit deliverable condition the user listed (numbered / bulleted / multi-command) is judged satisfied / unsatisfied / insufficient evidence, with tool evidence as the source of truth \u2014 a reply claiming completion is not evidence. Off: results are only logged, the conversation is not interrupted. On: when a condition is confidently judged unsatisfied, one prompt is appended on your behalf asking the AI to review and finish it; insufficient evidence never triggers a re-prompt, and at most one per turn.",
291
+ completeResultOk: "conditions met",
292
+ completeResultUnsatisfied: "unmet found",
293
+ completeConditions: "conditions",
294
+ completeSatisfied: "met",
295
+ completeUnsatisfied: "unmet",
296
+ completeInsufficient: "insufficient",
297
+ completeSteered: "re-prompted",
240
298
  backend: "Decision backend",
241
299
  url: "Server URL",
242
300
  model: "Model",
@@ -256,53 +314,319 @@ var dictionaries = {
256
314
  unavailable: "Connection unavailable",
257
315
  error: "Request failed. Please retry.",
258
316
  loading: "Loading session settings",
259
- capacity: "Session capacity reached: automatic intervention is paused here; host permission rules still apply.",
260
317
  destination: "Decisions send the necessary evaluation context to this backend:",
261
- httpWarning: "This address uses unencrypted HTTP. Your API key and evaluation context will be sent in plaintext to this address."
318
+ httpWarning: "This address uses unencrypted HTTP. Your API key and evaluation context will be sent in plaintext to this address.",
319
+ analysisTab: "Decision analysis",
320
+ analysisTitle: "Decision analysis",
321
+ analysisIntro: "A read-only aggregate and manual annotation over this session\u2019s persisted decision log. It changes no agent behavior.",
322
+ analysisProfile: "Process profile",
323
+ analysisMeanScore: "Mean score",
324
+ analysisTrend: "Quality-trend backtest",
325
+ analysisTrendWarn: "Warn fires",
326
+ analysisTrendSevere: "Severe fires",
327
+ analysisTrendMaxRun: "Longest low run",
328
+ analysisTrendNote: "Replays this session\u2019s self-check scores in time order under the current thresholds to count how often the alarm would fire \u2014 a calibration aid, not a live signal.",
329
+ analysisAccuracy: "Decision accuracy",
330
+ analysisAccuracyEmpty: "No decisions rated yet. Rate rows below as correct / wrong / unsure and a summary appears here.",
331
+ analysisLogNote: "Ratings here share one source with the intervention panel: a rating on either surface writes to the same log, keyed by decision id, latest wins.",
332
+ analysisLogTruncated: "No decision rows to show for this session (the in-memory log may have rolled over)."
262
333
  }
263
334
  };
264
335
 
265
336
  // src/client/panel.tsx
266
- var import_react = require("react");
267
- var import_jsx_runtime = require("react/jsx-runtime");
268
- var api = "/plugins/dsh-decision-layer/api";
337
+ var import_react2 = require("react");
338
+
339
+ // src/client/api.ts
340
+ var API_BASE = "/plugins/dsh-decision-layer/api";
269
341
  async function request(path, init) {
270
- const response = await fetch(`${api}/${path}`, init);
342
+ const response = await fetch(`${API_BASE}/${path}`, init);
271
343
  const body = await response.json();
272
344
  if (!response.ok || !body.ok || body.value === void 0) throw new Error(body.error || "Request failed");
273
345
  return body.value;
274
346
  }
347
+
348
+ // src/client/decision-log.tsx
349
+ var import_jsx_runtime = require("react/jsx-runtime");
350
+ var time = (at) => new Date(at).toLocaleTimeString();
351
+ function reasonText(t, reason) {
352
+ return reason === "unavailable" || reason === "unreachable" ? t("reasonUnavailable") : reason === "http-error" ? t("reasonHttpError") : reason === "invalid-response" ? t("reasonInvalidResponse") : reason === "low-confidence" ? t("reasonLowConfidence") : reason === "too-many-candidates" ? t("reasonTooMany") : reason === "low-keep" ? t("reasonLowKeep") : reason === "config" ? t("reasonConfig") : reason;
353
+ }
354
+ function actionText(t, action) {
355
+ return action === "deny" ? t("actionDeny") : action === "ask" ? t("actionAsk") : action === "pass" ? t("actionPass") : action;
356
+ }
357
+ function failDetail(t, entry) {
358
+ const reason = reasonText(t, entry.reason);
359
+ if (entry.reason === "low-confidence" && typeof entry.confidence === "number") return `${reason}\uFF08${t("logConfidence")} ${entry.confidence}\uFF09`;
360
+ return reason;
361
+ }
362
+ function renderGate({ t }, entry) {
363
+ const tool = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "decision-log-tool", children: entry.tool ?? "\u2014" });
364
+ if (entry.outcome === "error") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
365
+ t("logGate"),
366
+ " \xB7 ",
367
+ tool,
368
+ " ",
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
370
+ t("gatePassThrough"),
371
+ entry.reason ? ` \xB7 ${failDetail(t, entry)}` : ""
372
+ ] })
373
+ ] });
374
+ if (entry.outcome === "deny") {
375
+ const suggestion2 = entry.suggestion && entry.suggestion !== "deny" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
376
+ " \xB7 ",
377
+ t("logSuggested"),
378
+ " ",
379
+ actionText(t, entry.suggestion)
380
+ ] }) : null;
381
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
382
+ t("logGate"),
383
+ " \xB7 ",
384
+ tool,
385
+ " ",
386
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-verdict", children: [
387
+ "\u2192 ",
388
+ t("actionDeny")
389
+ ] }),
390
+ suggestion2
391
+ ] });
392
+ }
393
+ const suggestion = entry.suggestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
394
+ " \xB7 ",
395
+ t("logSuggested"),
396
+ " ",
397
+ actionText(t, entry.suggestion)
398
+ ] }) : null;
399
+ const lowConf = entry.reason === "low-confidence" && typeof entry.confidence === "number" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
400
+ " \xB7 ",
401
+ t("reasonLowConfidence"),
402
+ " ",
403
+ entry.confidence
404
+ ] }) : null;
405
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
406
+ t("logGate"),
407
+ " \xB7 ",
408
+ tool,
409
+ " ",
410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-verdict", children: t("gatePassThrough") }),
411
+ suggestion,
412
+ lowConf
413
+ ] });
414
+ }
415
+ function renderCheck({ t }, entry) {
416
+ const label = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { title: t("logCheckHint"), children: t("logCheck") });
417
+ if (entry.outcome === "error") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
418
+ label,
419
+ " ",
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
421
+ t("logEvalFailed"),
422
+ entry.reason ? ` \xB7 ${failDetail(t, entry)}` : ""
423
+ ] })
424
+ ] });
425
+ const result = entry.outcome === "low" ? t("checkResultLow") : t("checkResultOk");
426
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
427
+ label,
428
+ " \xB7 ",
429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-verdict", children: result }),
430
+ entry.score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", title: t("logScoreHint"), children: [
431
+ " \xB7 ",
432
+ t("logScore"),
433
+ " ",
434
+ entry.score,
435
+ "/2"
436
+ ] }) : null,
437
+ typeof entry.confidence === "number" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
438
+ " \xB7 ",
439
+ t("logConfidence"),
440
+ " ",
441
+ entry.confidence
442
+ ] }) : null
443
+ ] });
444
+ }
445
+ function renderNarrow({ t }, entry) {
446
+ const label = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { title: t("logNarrowHint"), children: t("logNarrow") });
447
+ if (entry.outcome === "error") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
448
+ label,
449
+ " ",
450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
451
+ t("logEvalFailed"),
452
+ entry.reason ? ` \xB7 ${failDetail(t, entry)}` : ""
453
+ ] })
454
+ ] });
455
+ const dropped = entry.dropped ?? 0;
456
+ const kept = typeof entry.kept === "number" ? entry.kept : void 0;
457
+ const guardText = entry.reason === "too-many-candidates" ? typeof entry.candidates === "number" ? t("reasonTooManyCount", { count: entry.candidates }) : t("reasonTooMany") : entry.reason === "low-keep" ? t("reasonLowKeep") : void 0;
458
+ const guard = guardText ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
459
+ " \xB7 ",
460
+ guardText
461
+ ] }) : null;
462
+ const modeTag = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
463
+ " \xB7 ",
464
+ entry.mode === "enforce" ? t("narrowEnforce") : t("narrowObserve")
465
+ ] });
466
+ if (dropped === 0) {
467
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
468
+ label,
469
+ " \xB7 ",
470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-verdict", children: t("narrowKept") }),
471
+ kept !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
472
+ " \xB7 ",
473
+ t("narrowKeptCount"),
474
+ " ",
475
+ kept
476
+ ] }) : null,
477
+ guard,
478
+ modeTag
479
+ ] });
480
+ }
481
+ const names = Array.isArray(entry.tools) && entry.tools.length > 0 ? entry.tools.join("\u3001") : void 0;
482
+ const overflow = names && Array.isArray(entry.tools) && entry.tools.length < dropped ? t("narrowMore", { count: dropped }) : "";
483
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
484
+ label,
485
+ " \xB7 ",
486
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-verdict", children: [
487
+ t("narrowDropped"),
488
+ " ",
489
+ dropped,
490
+ kept !== void 0 ? ` / ${t("narrowKeptCount")} ${kept}` : ""
491
+ ] }),
492
+ names ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
493
+ " \xB7 ",
494
+ names,
495
+ overflow
496
+ ] }) : null,
497
+ guard,
498
+ modeTag
499
+ ] });
500
+ }
501
+ function renderComplete({ t }, entry) {
502
+ const label = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("logComplete") });
503
+ if (entry.outcome === "error") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
504
+ label,
505
+ " ",
506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
507
+ t("logEvalFailed"),
508
+ entry.reason ? ` \xB7 ${failDetail(t, entry)}` : ""
509
+ ] })
510
+ ] });
511
+ const result = entry.outcome === "unsatisfied" ? t("completeResultUnsatisfied") : t("completeResultOk");
512
+ const parts = [];
513
+ if (typeof entry.satisfied === "number") parts.push(`${t("completeSatisfied")} ${entry.satisfied}`);
514
+ if (typeof entry.unsatisfied === "number" && entry.unsatisfied > 0) parts.push(`${t("completeUnsatisfied")} ${entry.unsatisfied}`);
515
+ if (typeof entry.insufficient === "number" && entry.insufficient > 0) parts.push(`${t("completeInsufficient")} ${entry.insufficient}`);
516
+ const tally = parts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
517
+ " \xB7 ",
518
+ parts.join(" / ")
519
+ ] }) : null;
520
+ const steer = entry.steered ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
521
+ " \xB7 ",
522
+ t("completeSteered")
523
+ ] }) : null;
524
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
525
+ label,
526
+ " \xB7 ",
527
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-verdict", children: result }),
528
+ typeof entry.conditions === "number" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
529
+ " \xB7 ",
530
+ t("completeConditions"),
531
+ " ",
532
+ entry.conditions
533
+ ] }) : null,
534
+ tally,
535
+ steer
536
+ ] });
537
+ }
538
+ function outcomeTagOf(entry) {
539
+ if (entry.kind === "narrow") return entry.outcome === "error" ? "error" : "ok";
540
+ if (entry.kind === "complete") return entry.outcome === "error" ? "error" : entry.outcome === "unsatisfied" ? "warn" : "ok";
541
+ return entry.outcome === "deny" ? "deny" : entry.outcome === "error" ? "error" : entry.outcome === "low" ? "warn" : "ok";
542
+ }
543
+ function renderBody(props, entry) {
544
+ return entry.kind === "gate" ? renderGate(props, entry) : entry.kind === "narrow" ? renderNarrow(props, entry) : entry.kind === "complete" ? renderComplete(props, entry) : renderCheck(props, entry);
545
+ }
546
+ function RatingButtons({ id, t, ratings, onRate }) {
547
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-rate", role: "group", "aria-label": t("rateGroup"), children: [
548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: `decision-rate-btn${ratings[id] === "good" ? " decision-rate-on decision-rate-good" : ""}`, "aria-label": t("rateGood"), title: t("rateGood"), "aria-pressed": ratings[id] === "good", onClick: () => onRate(id, "good"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
549
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 7.2 8.1 2.4c.6-.9 2-.5 2 .6V6h3.2c.9 0 1.5.8 1.3 1.6l-1.1 4.6c-.2.8-.9 1.3-1.7 1.3H5V7.2Z", stroke: "currentColor", "stroke-width": "1.2", "stroke-linejoin": "round" }),
550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 7.2H2.6v6.3H5", stroke: "currentColor", "stroke-width": "1.2", "stroke-linejoin": "round" })
551
+ ] }) }),
552
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: `decision-rate-btn${ratings[id] === "bad" ? " decision-rate-on decision-rate-bad" : ""}`, "aria-label": t("rateBad"), title: t("rateBad"), "aria-pressed": ratings[id] === "bad", onClick: () => onRate(id, "bad"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11 8.8 7.9 13.6c-.6.9-2 .5-2-.6V10H2.7c-.9 0-1.5-.8-1.3-1.6l1.1-4.6C2.7 3 3.4 2.5 4.2 2.5H11v6.3Z", stroke: "currentColor", "stroke-width": "1.2", "stroke-linejoin": "round" }),
554
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11 8.8h2.4V2.5H11", stroke: "currentColor", "stroke-width": "1.2", "stroke-linejoin": "round" })
555
+ ] }) }),
556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: `decision-rate-btn${ratings[id] === "unsure" ? " decision-rate-on decision-rate-unsure" : ""}`, "aria-label": t("rateUnsure"), title: t("rateUnsure"), "aria-pressed": ratings[id] === "unsure", onClick: () => onRate(id, "unsure"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "8", r: "6.2", stroke: "currentColor", "stroke-width": "1.2" }),
558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6.3 6.2c0-1 .8-1.7 1.7-1.7s1.7.7 1.7 1.6c0 1.3-1.6 1.4-1.7 2.6", stroke: "currentColor", "stroke-width": "1.2", "stroke-linecap": "round", "stroke-linejoin": "round" }),
559
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "11.4", r: ".8", fill: "currentColor" })
560
+ ] }) })
561
+ ] });
562
+ }
563
+ function DecisionLogList({ entries, ratings, onRate, t, newestFirst = true }) {
564
+ const rows = newestFirst ? entries.slice().reverse() : entries.slice();
565
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "decision-log", children: rows.map((entry, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: `decision-log-item decision-log-${outcomeTagOf(entry)}`, children: [
566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-time", children: time(entry.at) }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-body", children: renderBody({ t }, entry) }),
568
+ entry.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RatingButtons, { id: entry.id, t, ratings, onRate })
569
+ ] }, entry.id ?? `${entry.at}-${index}`)) });
570
+ }
571
+
572
+ // src/client/use-annotations.ts
573
+ var import_react = require("react");
574
+ function useAnnotations(sessionId, onFail) {
575
+ const [ratings, setRatings] = (0, import_react.useState)({});
576
+ const seed = (0, import_react.useCallback)((analysis) => {
577
+ setRatings(analysis?.ratings ?? {});
578
+ }, []);
579
+ const annotate = (0, import_react.useCallback)(async (id, rating) => {
580
+ setRatings((prev) => ({ ...prev, [id]: rating }));
581
+ try {
582
+ await request("annotate", {
583
+ method: "POST",
584
+ headers: { "content-type": "application/json" },
585
+ body: JSON.stringify({ sessionId, target: id, rating })
586
+ });
587
+ } catch {
588
+ onFail();
589
+ setRatings((prev) => {
590
+ const next = { ...prev };
591
+ delete next[id];
592
+ return next;
593
+ });
594
+ }
595
+ }, [sessionId, onFail]);
596
+ return { ratings, seed, annotate };
597
+ }
598
+
599
+ // src/client/panel.tsx
600
+ var import_jsx_runtime2 = require("react/jsx-runtime");
275
601
  function DecisionPanel(props) {
276
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SessionPanel, { ...props }, props.sessionId);
602
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SessionPanel, { ...props }, props.sessionId);
277
603
  }
278
604
  function Bar({ allow, ask, deny }) {
279
605
  const total = allow + ask + deny;
280
- if (total === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "decision-bar decision-bar-empty", "aria-hidden": "true" });
606
+ if (total === 0) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "decision-bar decision-bar-empty", "aria-hidden": "true" });
281
607
  const pct = (n) => `${n / total * 100}%`;
282
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-bar", "aria-hidden": "true", children: [
283
- allow > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-bar-allow", style: { width: pct(allow) } }),
284
- ask > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-bar-ask", style: { width: pct(ask) } }),
285
- deny > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-bar-deny", style: { width: pct(deny) } })
608
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-bar", "aria-hidden": "true", children: [
609
+ allow > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-bar-allow", style: { width: pct(allow) } }),
610
+ ask > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-bar-ask", style: { width: pct(ask) } }),
611
+ deny > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-bar-deny", style: { width: pct(deny) } })
286
612
  ] });
287
613
  }
288
614
  function SessionPanel({ sessionId, t }) {
289
- const dialog = (0, import_react.useRef)(null);
290
- const trigger = (0, import_react.useRef)(null);
291
- const [open, setOpen] = (0, import_react.useState)(false);
292
- const [enabled, setEnabled] = (0, import_react.useState)(null);
293
- const [capacityExceeded, setCapacityExceeded] = (0, import_react.useState)(false);
294
- const [busy, setBusy] = (0, import_react.useState)(false);
295
- const [metrics, setMetrics] = (0, import_react.useState)(null);
296
- const [log, setLog] = (0, import_react.useState)([]);
297
- const [status, setStatus] = (0, import_react.useState)("idle");
298
- const [message, setMessage] = (0, import_react.useState)("");
299
- (0, import_react.useEffect)(() => {
615
+ const dialog = (0, import_react2.useRef)(null);
616
+ const trigger = (0, import_react2.useRef)(null);
617
+ const [open, setOpen] = (0, import_react2.useState)(false);
618
+ const [enabled, setEnabled] = (0, import_react2.useState)(null);
619
+ const [busy, setBusy] = (0, import_react2.useState)(false);
620
+ const [metrics, setMetrics] = (0, import_react2.useState)(null);
621
+ const [log, setLog] = (0, import_react2.useState)([]);
622
+ const [analysis, setAnalysis] = (0, import_react2.useState)(null);
623
+ const [status, setStatus] = (0, import_react2.useState)("idle");
624
+ const [message, setMessage] = (0, import_react2.useState)("");
625
+ const { ratings, seed, annotate } = useAnnotations(sessionId, () => setMessage(t("annotateFailed")));
626
+ (0, import_react2.useEffect)(() => {
300
627
  let live = true;
301
628
  void request(`session?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
302
- if (live) {
303
- setEnabled(value.enabled);
304
- setCapacityExceeded(Boolean(value.capacityExceeded));
305
- }
629
+ if (live) setEnabled(value.enabled);
306
630
  }).catch(() => {
307
631
  if (live) setMessage(t("error"));
308
632
  });
@@ -323,7 +647,7 @@ function SessionPanel({ sessionId, t }) {
323
647
  if (live()) setStatus("down");
324
648
  });
325
649
  };
326
- (0, import_react.useEffect)(() => {
650
+ (0, import_react2.useEffect)(() => {
327
651
  if (!open) return;
328
652
  const element = dialog.current;
329
653
  if (!element) return;
@@ -338,11 +662,18 @@ function SessionPanel({ sessionId, t }) {
338
662
  if (alive) setLog(value.entries);
339
663
  }).catch(() => {
340
664
  });
665
+ void request(`logs?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
666
+ if (alive) {
667
+ setAnalysis(value);
668
+ seed(value);
669
+ }
670
+ }).catch(() => {
671
+ });
341
672
  return () => {
342
673
  alive = false;
343
674
  element.close();
344
675
  };
345
- }, [open, t, sessionId]);
676
+ }, [open, t, sessionId, seed]);
346
677
  const updateEnabled = async (next) => {
347
678
  setBusy(true);
348
679
  try {
@@ -352,159 +683,34 @@ function SessionPanel({ sessionId, t }) {
352
683
  body: JSON.stringify({ sessionId, enabled: next })
353
684
  });
354
685
  setEnabled(value.enabled);
355
- setCapacityExceeded(Boolean(value.capacityExceeded));
356
686
  } catch {
357
687
  setMessage(t("error"));
358
688
  } finally {
359
689
  setBusy(false);
360
690
  }
361
691
  };
362
- const time = (at) => new Date(at).toLocaleTimeString();
363
- const reasonText = (reason) => reason === "unavailable" || reason === "unreachable" ? t("reasonUnavailable") : reason === "http-error" ? t("reasonHttpError") : reason === "invalid-response" ? t("reasonInvalidResponse") : reason === "low-confidence" ? t("reasonLowConfidence") : reason === "capacity" ? t("reasonCapacity") : reason === "too-many-candidates" ? t("reasonTooMany") : reason === "low-keep" ? t("reasonLowKeep") : reason === "config" ? t("reasonConfig") : reason;
364
- const actionText = (action) => action === "deny" ? t("actionDeny") : action === "ask" ? t("actionAsk") : action;
365
- const failDetail = (entry) => {
366
- const reason = reasonText(entry.reason);
367
- if (entry.reason === "low-confidence" && typeof entry.confidence === "number") return `${reason}\uFF08${t("logConfidence")} ${entry.confidence}\uFF09`;
368
- return reason;
369
- };
370
- const outcomeTag = (entry) => entry.outcome === "deny" ? "deny" : entry.outcome === "error" ? "error" : entry.outcome === "low" ? "warn" : "ok";
371
- const renderGate = (entry) => {
372
- const tool = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "decision-log-tool", children: entry.tool ?? "\u2014" });
373
- if (entry.outcome === "error") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
374
- t("logGate"),
375
- " \xB7 ",
376
- tool,
377
- " ",
378
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
379
- t("logEvalFailed"),
380
- entry.reason ? ` \xB7 ${failDetail(entry)}` : ""
381
- ] })
382
- ] });
383
- const verdict = entry.outcome === "deny" ? t("actionDeny") : actionText(entry.action) ?? entry.outcome;
384
- const suggestion = entry.suggestion && entry.suggestion !== entry.action ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
385
- " \xB7 ",
386
- t("logSuggested"),
387
- " ",
388
- actionText(entry.suggestion)
389
- ] }) : null;
390
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
391
- t("logGate"),
392
- " \xB7 ",
393
- tool,
394
- " ",
395
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-verdict", children: [
396
- "\u2192 ",
397
- verdict
398
- ] }),
399
- suggestion
400
- ] });
401
- };
402
- const renderCheck = (entry) => {
403
- const label = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { title: t("logCheckHint"), children: t("logCheck") });
404
- if (entry.outcome === "error") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
405
- label,
406
- " ",
407
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
408
- t("logEvalFailed"),
409
- entry.reason ? ` \xB7 ${failDetail(entry)}` : ""
410
- ] })
411
- ] });
412
- const result = entry.outcome === "low" ? t("checkResultLow") : t("checkResultOk");
413
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
414
- label,
415
- " \xB7 ",
416
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-verdict", children: result }),
417
- entry.score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", title: t("logScoreHint"), children: [
418
- " \xB7 ",
419
- t("logScore"),
420
- " ",
421
- entry.score,
422
- "/2"
423
- ] }) : null,
424
- typeof entry.confidence === "number" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
425
- " \xB7 ",
426
- t("logConfidence"),
427
- " ",
428
- entry.confidence
429
- ] }) : null
430
- ] });
431
- };
432
- const renderNarrow = (entry) => {
433
- const label = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { title: t("logNarrowHint"), children: t("logNarrow") });
434
- if (entry.outcome === "error") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
435
- label,
436
- " ",
437
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
438
- t("logEvalFailed"),
439
- entry.reason ? ` \xB7 ${failDetail(entry)}` : ""
440
- ] })
441
- ] });
442
- const dropped = entry.dropped ?? 0;
443
- const kept = typeof entry.kept === "number" ? entry.kept : void 0;
444
- const guardText = entry.reason === "too-many-candidates" ? typeof entry.candidates === "number" ? t("reasonTooManyCount", { count: entry.candidates }) : t("reasonTooMany") : entry.reason === "low-keep" ? t("reasonLowKeep") : void 0;
445
- const guard = guardText ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
446
- " \xB7 ",
447
- guardText
448
- ] }) : null;
449
- const modeTag = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
450
- " \xB7 ",
451
- entry.mode === "enforce" ? t("narrowEnforce") : t("narrowObserve")
452
- ] });
453
- if (dropped === 0) {
454
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
455
- label,
456
- " \xB7 ",
457
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-verdict", children: t("narrowKept") }),
458
- kept !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
459
- " \xB7 ",
460
- t("narrowKeptCount"),
461
- " ",
462
- kept
463
- ] }) : null,
464
- guard,
465
- modeTag
466
- ] });
467
- }
468
- const names = Array.isArray(entry.tools) && entry.tools.length > 0 ? entry.tools.join("\u3001") : void 0;
469
- const overflow = names && Array.isArray(entry.tools) && entry.tools.length < dropped ? t("narrowMore", { count: dropped }) : "";
470
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
471
- label,
472
- " \xB7 ",
473
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-verdict", children: [
474
- t("narrowDropped"),
475
- " ",
476
- dropped,
477
- kept !== void 0 ? ` / ${t("narrowKeptCount")} ${kept}` : ""
478
- ] }),
479
- names ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-log-detail", children: [
480
- " \xB7 ",
481
- names,
482
- overflow
483
- ] }) : null,
484
- guard,
485
- modeTag
486
- ] });
487
- };
488
- const outcomeTagOf = (entry) => entry.kind === "narrow" ? entry.outcome === "error" ? "error" : entry.outcome === "applied" ? "ok" : "ok" : outcomeTag(entry);
692
+ const time2 = (at) => new Date(at).toLocaleTimeString();
489
693
  const statusLabel = status === "ok" ? t("statusOk") : status === "down" ? t("statusDown") : status === "checking" ? t("statusChecking") : t("statusIdle");
490
694
  const attempts = metrics?.attempts ?? 0;
695
+ const severity = metrics?.check?.trend?.severity ?? "normal";
696
+ const trendTitle = severity === "severe" ? t("trendSevereHint") : severity === "warn" ? t("trendWarnHint") : void 0;
491
697
  const checkValid = metrics?.check ? metrics.check.attempts - metrics.check.failures : 0;
492
698
  const checkPassRate = metrics?.check && checkValid > 0 ? Math.round((checkValid - metrics.check.low) / checkValid * 100) : null;
493
699
  const narrowValid = metrics?.narrow ? metrics.narrow.attempts - metrics.narrow.failures : 0;
494
700
  const narrowApplyRate = metrics?.narrow && narrowValid > 0 ? Math.round(metrics.narrow.applied / narrowValid * 100) : null;
495
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
496
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { ref: trigger, className: "decision-trigger", type: "button", "aria-haspopup": "dialog", "aria-expanded": open, "aria-busy": enabled === null, "aria-label": t("button"), onClick: () => setOpen(true), children: [
497
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "decision-trigger-icon", width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
498
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 1.5 2 4.2v3.6c0 3.3 2.3 5.6 6 6.7 3.7-1.1 6-3.4 6-6.7V4.2L8 1.5Z", stroke: "currentColor", "stroke-width": "1.3", "stroke-linejoin": "round" }),
499
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m5.6 8 1.7 1.8L10.6 6.3", stroke: "currentColor", "stroke-width": "1.3", "stroke-linecap": "round", "stroke-linejoin": "round" })
701
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
702
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { ref: trigger, className: `decision-trigger decision-trend-${severity}`, type: "button", "aria-haspopup": "dialog", "aria-expanded": open, "aria-busy": enabled === null, "aria-label": t("button"), title: trendTitle, onClick: () => setOpen(true), children: [
703
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { className: "decision-trigger-icon", width: "15", height: "15", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
704
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "8", r: "6.4", stroke: "currentColor", "stroke-width": "1.3" }),
705
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 4.6 11.3 11H4.7L8 4.6Z", fill: "currentColor", stroke: "none" })
500
706
  ] }),
501
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-trigger-text", children: [
707
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-trigger-text", children: [
502
708
  t("triggerDecisions"),
503
709
  " ",
504
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: attempts })
710
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: attempts })
505
711
  ] })
506
712
  ] }),
507
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
713
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
508
714
  "dialog",
509
715
  {
510
716
  ref: dialog,
@@ -529,150 +735,174 @@ function SessionPanel({ sessionId, t }) {
529
735
  trigger.current?.focus();
530
736
  },
531
737
  children: [
532
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-head", children: [
533
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "decision-panel-title", children: t("title") }),
534
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => {
738
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-head", children: [
739
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { id: "decision-panel-title", children: t("title") }),
740
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: () => {
535
741
  dialog.current?.close();
536
742
  }, "aria-label": t("close"), children: "\xD7" })
537
743
  ] }),
538
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-body", children: [
539
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
540
- enabled === null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: t("loading") }),
541
- capacityExceeded && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", children: t("capacity") }),
542
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "decision-toggle", children: [
543
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-toggle-text", children: t("enabled") }),
544
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-toggle-box", children: [
545
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
744
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-body", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
746
+ enabled === null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: t("loading") }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-toggle", children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-toggle-text", children: t("enabled") }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-toggle-box", children: [
750
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
546
751
  "input",
547
752
  {
548
753
  className: "decision-toggle-input",
549
754
  type: "checkbox",
550
755
  checked: enabled === true,
551
- disabled: busy || enabled === null || capacityExceeded,
756
+ disabled: busy || enabled === null,
552
757
  onChange: (event) => void updateEnabled(event.target.checked)
553
758
  }
554
759
  ),
555
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-toggle-track", "aria-hidden": "true" })
760
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-toggle-track", "aria-hidden": "true" })
556
761
  ] })
557
762
  ] }),
558
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-status", children: [
559
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `decision-status-dot decision-status-${status}`, "aria-hidden": "true" }),
560
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-status-label", children: [
763
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-status", children: [
764
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `decision-status-dot decision-status-${status}`, "aria-hidden": "true" }),
765
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-status-label", children: [
561
766
  t("backendStatus"),
562
767
  "\uFF1A",
563
768
  statusLabel
564
769
  ] }),
565
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "decision-status-recheck", disabled: status === "checking", onClick: () => checkStatus(() => true), children: t("recheck") })
770
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "decision-status-recheck", disabled: status === "checking", onClick: () => checkStatus(() => true), children: t("recheck") })
566
771
  ] })
567
772
  ] }),
568
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
569
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("metrics") }),
570
- !metrics?.gate && !metrics?.check && !metrics?.narrow ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "decision-empty", role: "status", children: t("empty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-cards", children: [
571
- metrics.gate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: "decision-card decision-card-wide", children: [
572
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "decision-card-head", children: [
573
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-card-title", children: t("cardGate") }),
574
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-card-total", children: [
773
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
774
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: t("metrics") }),
775
+ !metrics?.gate && !metrics?.check && !metrics?.narrow && !metrics?.complete ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-empty", role: "status", children: t("empty") }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-cards", children: [
776
+ metrics.gate && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { className: "decision-card decision-card-wide", children: [
777
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "decision-card-head", children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-card-title", children: t("cardGate") }),
779
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-card-total", children: [
575
780
  metrics.gate.attempts,
576
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("cardTimes") })
781
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: t("cardTimes") })
577
782
  ] })
578
783
  ] }),
579
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Bar, { allow: metrics.gate.allow, ask: metrics.gate.ask, deny: metrics.gate.deny }),
580
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { className: "decision-legend", children: [
581
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
582
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-legend-dot decision-dot-allow", "aria-hidden": "true" }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Bar, { allow: metrics.gate.allow, ask: metrics.gate.ask, deny: metrics.gate.deny }),
785
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("ul", { className: "decision-legend", children: [
786
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { children: [
787
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-legend-dot decision-dot-allow", "aria-hidden": "true" }),
583
788
  t("allow"),
584
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.allow })
789
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: metrics.gate.allow })
585
790
  ] }),
586
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
587
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-legend-dot decision-dot-ask", "aria-hidden": "true" }),
791
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { children: [
792
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-legend-dot decision-dot-ask", "aria-hidden": "true" }),
588
793
  t("ask"),
589
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.ask })
794
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: metrics.gate.ask })
590
795
  ] }),
591
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
592
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-legend-dot decision-dot-deny", "aria-hidden": "true" }),
796
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { children: [
797
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-legend-dot decision-dot-deny", "aria-hidden": "true" }),
593
798
  t("deny"),
594
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.deny })
799
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: metrics.gate.deny })
595
800
  ] })
596
801
  ] }),
597
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "decision-card-foot", children: [
598
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
802
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("footer", { className: "decision-card-foot", children: [
803
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
599
804
  t("actualShort"),
600
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("b", { children: [
805
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("b", { children: [
601
806
  metrics.gate.actual.allow,
602
807
  " / ",
603
808
  metrics.gate.actual.deny
604
809
  ] })
605
810
  ] }),
606
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
607
812
  t("failures"),
608
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.failures })
813
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: metrics.gate.failures })
609
814
  ] })
610
815
  ] })
611
816
  ] }),
612
- metrics.check && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: "decision-card", children: [
613
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "decision-card-head", children: [
614
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-card-title", children: t("cardCheck") }),
615
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-card-total", children: [
817
+ metrics.check && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { className: "decision-card", children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "decision-card-head", children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-card-title", children: t("cardCheck") }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-card-total", children: [
616
821
  metrics.check.attempts,
617
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("cardTimes") })
822
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: t("cardTimes") })
618
823
  ] })
619
824
  ] }),
620
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-card-stats", children: [
621
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-stat", children: [
622
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-num decision-stat-warn", children: metrics.check.low }),
623
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-label", children: t("checkLow") })
825
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-card-stats", children: [
826
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
827
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num decision-stat-warn", children: metrics.check.low }),
828
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("checkLow") })
624
829
  ] }),
625
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-stat", children: [
626
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-num", children: metrics.check.attempts - metrics.check.low - metrics.check.failures }),
627
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-label", children: t("checkOk") })
830
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num", children: metrics.check.attempts - metrics.check.low - metrics.check.failures }),
832
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("checkOk") })
628
833
  ] }),
629
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-stat", children: [
630
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-num decision-stat-muted", children: metrics.check.failures }),
631
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-label", children: t("failures") })
834
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
835
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num decision-stat-muted", children: metrics.check.failures }),
836
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("failures") })
632
837
  ] })
633
838
  ] }),
634
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("footer", { className: "decision-card-foot decision-card-rate", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
839
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("footer", { className: "decision-card-foot decision-card-rate", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
635
840
  t("checkPassRate"),
636
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: checkPassRate === null ? t("rateNa") : `${checkPassRate}%` })
841
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: checkPassRate === null ? t("rateNa") : `${checkPassRate}%` })
637
842
  ] }) })
638
843
  ] }),
639
- metrics.narrow && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: "decision-card", children: [
640
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "decision-card-head", children: [
641
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-card-title", children: t("cardNarrow") }),
642
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-card-total", children: [
844
+ metrics.narrow && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { className: "decision-card", children: [
845
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "decision-card-head", children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-card-title", children: t("cardNarrow") }),
847
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-card-total", children: [
643
848
  metrics.narrow.attempts,
644
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("cardTimes") })
849
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: t("cardTimes") })
645
850
  ] })
646
851
  ] }),
647
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-card-stats", children: [
648
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-stat", children: [
649
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-num", children: metrics.narrow.applied }),
650
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-label", children: t("narrowAppliedLabel") })
852
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-card-stats", children: [
853
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
854
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num", children: metrics.narrow.applied }),
855
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("narrowAppliedLabel") })
651
856
  ] }),
652
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-stat", children: [
653
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-num", children: metrics.narrow.dropped }),
654
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-label", children: t("narrowDroppedLabel") })
857
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
858
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num", children: metrics.narrow.dropped }),
859
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("narrowDroppedLabel") })
655
860
  ] }),
656
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decision-stat", children: [
657
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-num decision-stat-muted", children: metrics.narrow.failures }),
658
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-stat-label", children: t("failures") })
861
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
862
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num decision-stat-muted", children: metrics.narrow.failures }),
863
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("failures") })
659
864
  ] })
660
865
  ] }),
661
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("footer", { className: "decision-card-foot decision-card-rate", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
866
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("footer", { className: "decision-card-foot decision-card-rate", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
662
867
  t("narrowApplyRate"),
663
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: narrowApplyRate === null ? t("rateNa") : `${narrowApplyRate}%` })
868
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: narrowApplyRate === null ? t("rateNa") : `${narrowApplyRate}%` })
869
+ ] }) })
870
+ ] }),
871
+ metrics.complete && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { className: "decision-card", children: [
872
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "decision-card-head", children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-card-title", children: t("cardComplete") }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-card-total", children: [
875
+ metrics.complete.attempts,
876
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: t("cardTimes") })
877
+ ] })
878
+ ] }),
879
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-card-stats", children: [
880
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
881
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num", children: metrics.complete.satisfied }),
882
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("completeSatisfied") })
883
+ ] }),
884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
885
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num decision-stat-warn", children: metrics.complete.unsatisfied }),
886
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("completeUnsatisfied") })
887
+ ] }),
888
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-stat", children: [
889
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-num decision-stat-muted", children: metrics.complete.insufficient }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-stat-label", children: t("completeInsufficient") })
891
+ ] })
892
+ ] }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("footer", { className: "decision-card-foot decision-card-rate", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
894
+ t("failures"),
895
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("b", { children: metrics.complete.failures })
664
896
  ] }) })
665
897
  ] })
666
898
  ] })
667
899
  ] }),
668
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
669
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: t("log") }),
670
- log.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "decision-empty", role: "status", children: t("logEmpty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "decision-log", children: log.slice().reverse().map((entry, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: `decision-log-item decision-log-${outcomeTagOf(entry)}`, children: [
671
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-time", children: time(entry.at) }),
672
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-log-body", children: entry.kind === "gate" ? renderGate(entry) : entry.kind === "narrow" ? renderNarrow(entry) : renderCheck(entry) })
673
- ] }, `${entry.at}-${index}`)) })
900
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: t("log") }),
902
+ log.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-empty", role: "status", children: t("logEmpty") }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DecisionLogList, { entries: log, ratings, onRate: (id, rating) => void annotate(id, rating), t }),
903
+ analysis && analysis.annotations.rated > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted decision-rate-summary", children: t("rateSummary", { rated: analysis.annotations.rated, good: analysis.annotations.good, bad: analysis.annotations.bad, unsure: analysis.annotations.unsure }) })
674
904
  ] }),
675
- message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: "decision-message decision-message-warn", children: message })
905
+ message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", className: "decision-message decision-message-warn", children: message })
676
906
  ] })
677
907
  ]
678
908
  }
@@ -680,12 +910,144 @@ function SessionPanel({ sessionId, t }) {
680
910
  ] });
681
911
  }
682
912
 
913
+ // src/client/analysis-view.tsx
914
+ var import_react3 = require("react");
915
+ var import_jsx_runtime3 = require("react/jsx-runtime");
916
+ function Stat({ label, value, tone }) {
917
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-stat", children: [
918
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `decision-stat-num${tone ? ` decision-stat-${tone}` : ""}`, children: value }),
919
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "decision-stat-label", children: label })
920
+ ] });
921
+ }
922
+ function AnalysisView({ sessionId, t }) {
923
+ const [analysis, setAnalysis] = (0, import_react3.useState)(null);
924
+ const [log, setLog] = (0, import_react3.useState)([]);
925
+ const [loaded, setLoaded] = (0, import_react3.useState)(false);
926
+ const [message, setMessage] = (0, import_react3.useState)("");
927
+ const { ratings, seed, annotate } = useAnnotations(sessionId, () => setMessage(t("annotateFailed")));
928
+ (0, import_react3.useEffect)(() => {
929
+ let alive = true;
930
+ setMessage("");
931
+ setLoaded(false);
932
+ void Promise.allSettled([
933
+ request(`logs?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
934
+ if (alive) {
935
+ setAnalysis(value);
936
+ seed(value);
937
+ }
938
+ }),
939
+ request(`log?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
940
+ if (alive) setLog(value.entries);
941
+ })
942
+ ]).finally(() => {
943
+ if (alive) setLoaded(true);
944
+ });
945
+ return () => {
946
+ alive = false;
947
+ };
948
+ }, [sessionId, seed]);
949
+ const profile = analysis?.profile;
950
+ const backtest = analysis?.trendBacktest;
951
+ const annotations = analysis?.annotations;
952
+ const meanScore = profile && profile.check.scoreCount > 0 ? Math.round(profile.check.scoreSum / profile.check.scoreCount * 10) / 10 : null;
953
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "decision-analysis", role: "region", "aria-label": t("analysisTitle"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-analysis-inner", children: [
954
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "decision-analysis-title", children: t("analysisTitle") }),
955
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "decision-muted decision-analysis-intro", children: t("analysisIntro") }),
956
+ loaded && (!analysis || analysis.totalDecisions === 0) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "decision-empty", role: "status", children: t("logEmpty") }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
957
+ profile && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "decision-analysis-section", children: [
958
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: t("analysisProfile") }),
959
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-analysis-cards", children: [
960
+ profile.gate.attempts > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { className: "decision-card", children: [
961
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "decision-card-head", children: [
962
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "decision-card-title", children: t("cardGate") }),
963
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "decision-card-total", children: [
964
+ profile.gate.attempts,
965
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: t("cardTimes") })
966
+ ] })
967
+ ] }),
968
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-card-stats", children: [
969
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("deny"), value: profile.gate.deny, tone: "warn" }),
970
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("allow"), value: profile.gate.allow }),
971
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("ask"), value: profile.gate.ask }),
972
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("failures"), value: profile.gate.error, tone: "muted" })
973
+ ] })
974
+ ] }),
975
+ profile.check.attempts > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { className: "decision-card", children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "decision-card-head", children: [
977
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "decision-card-title", children: t("cardCheck") }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "decision-card-total", children: [
979
+ profile.check.attempts,
980
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: t("cardTimes") })
981
+ ] })
982
+ ] }),
983
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-card-stats", children: [
984
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("checkLow"), value: profile.check.low, tone: "warn" }),
985
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("checkOk"), value: profile.check.ok }),
986
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("analysisMeanScore"), value: meanScore === null ? t("rateNa") : `${meanScore}/2` }),
987
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("failures"), value: profile.check.error, tone: "muted" })
988
+ ] })
989
+ ] }),
990
+ profile.narrow.attempts > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { className: "decision-card", children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "decision-card-head", children: [
992
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "decision-card-title", children: t("cardNarrow") }),
993
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "decision-card-total", children: [
994
+ profile.narrow.attempts,
995
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: t("cardTimes") })
996
+ ] })
997
+ ] }),
998
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-card-stats", children: [
999
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("narrowAppliedLabel"), value: profile.narrow.applied }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("narrowDroppedLabel"), value: profile.narrow.droppedSum }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("failures"), value: profile.narrow.error, tone: "muted" })
1002
+ ] })
1003
+ ] }),
1004
+ profile.complete.attempts > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { className: "decision-card", children: [
1005
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "decision-card-head", children: [
1006
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "decision-card-title", children: t("cardComplete") }),
1007
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "decision-card-total", children: [
1008
+ profile.complete.attempts,
1009
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("small", { children: t("cardTimes") })
1010
+ ] })
1011
+ ] }),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-card-stats", children: [
1013
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("completeSatisfied"), value: profile.complete.satisfied }),
1014
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("completeUnsatisfied"), value: profile.complete.unsatisfied, tone: "warn" }),
1015
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("completeInsufficient"), value: profile.complete.insufficient, tone: "muted" })
1016
+ ] })
1017
+ ] })
1018
+ ] })
1019
+ ] }),
1020
+ backtest && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "decision-analysis-section", children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: t("analysisTrend") }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "decision-card-stats decision-analysis-trend", children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("analysisTrendWarn"), value: backtest.warnHits }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("analysisTrendSevere"), value: backtest.severeHits, tone: "warn" }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stat, { label: t("analysisTrendMaxRun"), value: backtest.maxRun })
1026
+ ] }),
1027
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "decision-muted decision-analysis-note", children: t("analysisTrendNote") })
1028
+ ] }),
1029
+ annotations && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "decision-analysis-section", children: [
1030
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: t("analysisAccuracy") }),
1031
+ annotations.rated === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "decision-muted", children: t("analysisAccuracyEmpty") }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "decision-muted decision-rate-summary", children: t("rateSummary", { rated: annotations.rated, good: annotations.good, bad: annotations.bad, unsure: annotations.unsure }) })
1032
+ ] }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "decision-analysis-section", children: [
1034
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: t("log") }),
1035
+ log.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "decision-empty", role: "status", children: t("analysisLogTruncated") }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1036
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DecisionLogList, { entries: log, ratings, onRate: (id, rating) => void annotate(id, rating), t }),
1037
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "decision-muted decision-analysis-note", children: t("analysisLogNote") })
1038
+ ] })
1039
+ ] })
1040
+ ] }),
1041
+ message && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { role: "status", className: "decision-message decision-message-warn", children: message })
1042
+ ] }) });
1043
+ }
1044
+
683
1045
  // src/client/config-form.tsx
684
- var import_react2 = require("react");
685
- var import_jsx_runtime2 = require("react/jsx-runtime");
686
- var api2 = "/plugins/dsh-decision-layer/api";
1046
+ var import_react4 = require("react");
1047
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1048
+ var api = "/plugins/dsh-decision-layer/api";
687
1049
  async function request2(path, fetchFn, init) {
688
- const response = await fetchFn(`${api2}/${path}`, init);
1050
+ const response = await fetchFn(`${api}/${path}`, init);
689
1051
  const body = await response.json();
690
1052
  if (!response.ok || !body.ok || body.value === void 0) throw new Error(body.error || "Request failed");
691
1053
  return body.value;
@@ -699,31 +1061,31 @@ var maxCandidatesOf = (settings) => settings?.maxCandidates ?? DEFAULT_MAX_CANDI
699
1061
  var thresholdOf = (settings) => settings?.threshold ?? DEFAULT_THRESHOLD;
700
1062
  function HeadIcon({ name }) {
701
1063
  const glyphs = {
702
- features: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
703
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "3", width: "12", height: "4.2", rx: "2.1", stroke: "currentColor", "stroke-width": "1.3" }),
704
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "11.9", cy: "5.1", r: "1.25", fill: "currentColor" }),
705
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "8.8", width: "12", height: "4.2", rx: "2.1", stroke: "currentColor", "stroke-width": "1.3" }),
706
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "4.1", cy: "10.9", r: "1.25", fill: "currentColor" })
1064
+ features: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1065
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "2", y: "3", width: "12", height: "4.2", rx: "2.1", stroke: "currentColor", "stroke-width": "1.3" }),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "11.9", cy: "5.1", r: "1.25", fill: "currentColor" }),
1067
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "2", y: "8.8", width: "12", height: "4.2", rx: "2.1", stroke: "currentColor", "stroke-width": "1.3" }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "4.1", cy: "10.9", r: "1.25", fill: "currentColor" })
707
1069
  ] }),
708
- advanced: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
709
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2.5 5h11M2.5 11h11", stroke: "currentColor", "stroke-width": "1.3", "stroke-linecap": "round" }),
710
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "10.5", cy: "5", r: "1.9", fill: "var(--dsw-alias-bg-layer-2,#fff)", stroke: "currentColor", "stroke-width": "1.3" }),
711
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5.5", cy: "11", r: "1.9", fill: "var(--dsw-alias-bg-layer-2,#fff)", stroke: "currentColor", "stroke-width": "1.3" })
1070
+ advanced: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1071
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M2.5 5h11M2.5 11h11", stroke: "currentColor", "stroke-width": "1.3", "stroke-linecap": "round" }),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "10.5", cy: "5", r: "1.9", fill: "var(--dsw-alias-bg-layer-2,#fff)", stroke: "currentColor", "stroke-width": "1.3" }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "5.5", cy: "11", r: "1.9", fill: "var(--dsw-alias-bg-layer-2,#fff)", stroke: "currentColor", "stroke-width": "1.3" })
712
1074
  ] }),
713
- backend: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
714
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2.3", y: "2.6", width: "11.4", height: "4.6", rx: "1.1", stroke: "currentColor", "stroke-width": "1.3" }),
715
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2.3", y: "8.8", width: "11.4", height: "4.6", rx: "1.1", stroke: "currentColor", "stroke-width": "1.3" }),
716
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "4.9", r: ".7", fill: "currentColor" }),
717
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "11.1", r: ".7", fill: "currentColor" })
1075
+ backend: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1076
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "2.3", y: "2.6", width: "11.4", height: "4.6", rx: "1.1", stroke: "currentColor", "stroke-width": "1.3" }),
1077
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "2.3", y: "8.8", width: "11.4", height: "4.6", rx: "1.1", stroke: "currentColor", "stroke-width": "1.3" }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "5", cy: "4.9", r: ".7", fill: "currentColor" }),
1079
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "5", cy: "11.1", r: ".7", fill: "currentColor" })
718
1080
  ] })
719
1081
  };
720
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `decision-head-icon decision-head-icon-${name}`, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 16 16", fill: "none", children: glyphs[name] }) });
1082
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `decision-head-icon decision-head-icon-${name}`, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 16 16", fill: "none", children: glyphs[name] }) });
721
1083
  }
722
1084
  function Toggle({ label, checked, disabled, onChange }) {
723
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-toggle", children: [
724
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-toggle-text", children: label }),
725
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "decision-toggle-box", children: [
726
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1085
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "decision-toggle", children: [
1086
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "decision-toggle-text", children: label }),
1087
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "decision-toggle-box", children: [
1088
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
727
1089
  "input",
728
1090
  {
729
1091
  className: "decision-toggle-input",
@@ -733,42 +1095,46 @@ function Toggle({ label, checked, disabled, onChange }) {
733
1095
  onChange: (event) => onChange(event.target.checked)
734
1096
  }
735
1097
  ),
736
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-toggle-track", "aria-hidden": "true" })
1098
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "decision-toggle-track", "aria-hidden": "true" })
737
1099
  ] })
738
1100
  ] });
739
1101
  }
740
1102
  function ConfigForm({ t, fetchFn = fetch }) {
741
- const [config, setConfig] = (0, import_react2.useState)({ url: "", model: "", apiKeySet: false, httpApprovedUrl: "", effectiveUrl: "https://api.typesafe.ai" });
742
- const [checkMode, setCheckMode] = (0, import_react2.useState)("observe");
743
- const [narrowMode, setNarrowMode] = (0, import_react2.useState)("enforce");
744
- const [keepPrefixes, setKeepPrefixes] = (0, import_react2.useState)(DEFAULT_KEEP_PREFIXES.join("\n"));
745
- const [maxCandidates, setMaxCandidates] = (0, import_react2.useState)(String(DEFAULT_MAX_CANDIDATES));
746
- const [threshold, setThreshold] = (0, import_react2.useState)(String(DEFAULT_THRESHOLD));
747
- const [gateOn, setGateOn] = (0, import_react2.useState)(true);
748
- const [checkOn, setCheckOn] = (0, import_react2.useState)(true);
749
- const [narrowOn, setNarrowOn] = (0, import_react2.useState)(true);
750
- const [key, setKey] = (0, import_react2.useState)("");
751
- const [busy, setBusy] = (0, import_react2.useState)(false);
752
- const [advancedDirty, setAdvancedDirty] = (0, import_react2.useState)(false);
753
- const [backendDirty, setBackendDirty] = (0, import_react2.useState)(false);
754
- const [ready, setReady] = (0, import_react2.useState)(false);
755
- const [message, setMessage] = (0, import_react2.useState)(null);
756
- const [probeResult, setProbeResult] = (0, import_react2.useState)(null);
1103
+ const [config, setConfig] = (0, import_react4.useState)({ url: "", model: "", apiKeySet: false, httpApprovedUrl: "", effectiveUrl: "https://api.typesafe.ai" });
1104
+ const [checkMode, setCheckMode] = (0, import_react4.useState)("observe");
1105
+ const [narrowMode, setNarrowMode] = (0, import_react4.useState)("enforce");
1106
+ const [keepPrefixes, setKeepPrefixes] = (0, import_react4.useState)(DEFAULT_KEEP_PREFIXES.join("\n"));
1107
+ const [maxCandidates, setMaxCandidates] = (0, import_react4.useState)(String(DEFAULT_MAX_CANDIDATES));
1108
+ const [threshold, setThreshold] = (0, import_react4.useState)(String(DEFAULT_THRESHOLD));
1109
+ const [gateOn, setGateOn] = (0, import_react4.useState)(true);
1110
+ const [checkOn, setCheckOn] = (0, import_react4.useState)(true);
1111
+ const [narrowOn, setNarrowOn] = (0, import_react4.useState)(true);
1112
+ const [completeOn, setCompleteOn] = (0, import_react4.useState)(true);
1113
+ const [completeMode, setCompleteMode] = (0, import_react4.useState)("observe");
1114
+ const [key, setKey] = (0, import_react4.useState)("");
1115
+ const [busy, setBusy] = (0, import_react4.useState)(false);
1116
+ const [advancedDirty, setAdvancedDirty] = (0, import_react4.useState)(false);
1117
+ const [backendDirty, setBackendDirty] = (0, import_react4.useState)(false);
1118
+ const [ready, setReady] = (0, import_react4.useState)(false);
1119
+ const [message, setMessage] = (0, import_react4.useState)(null);
1120
+ const [probeResult, setProbeResult] = (0, import_react4.useState)(null);
757
1121
  const applyFeatures = (features) => {
758
1122
  setGateOn(features?.gate !== false);
759
1123
  setCheckOn(features?.check !== false);
760
1124
  setNarrowOn(features?.narrow !== false);
1125
+ setCompleteOn(features?.complete !== false);
761
1126
  };
762
1127
  const applyConfig = (value) => {
763
1128
  setConfig(value);
764
1129
  setCheckMode(value.checkSettings?.mode ?? "observe");
765
1130
  setNarrowMode(value.narrowSettings?.mode ?? "enforce");
1131
+ setCompleteMode(value.completeSettings?.mode ?? "observe");
766
1132
  setKeepPrefixes(prefixesText(value.narrowSettings));
767
1133
  setMaxCandidates(String(maxCandidatesOf(value.narrowSettings)));
768
1134
  setThreshold(String(thresholdOf(value.narrowSettings)));
769
1135
  applyFeatures(value.features);
770
1136
  };
771
- (0, import_react2.useEffect)(() => {
1137
+ (0, import_react4.useEffect)(() => {
772
1138
  let live = true;
773
1139
  setMessage(null);
774
1140
  setReady(false);
@@ -800,7 +1166,8 @@ function ConfigForm({ t, fetchFn = fetch }) {
800
1166
  body: JSON.stringify({
801
1167
  checkSettings: { mode: checkMode },
802
1168
  narrowSettings: { mode: narrowMode, keepPrefixes: parsePrefixes(keepPrefixes), maxCandidates: cap, threshold: thresh },
803
- features: { gate: gateOn, check: checkOn, narrow: narrowOn }
1169
+ completeSettings: { mode: completeMode },
1170
+ features: { gate: gateOn, check: checkOn, narrow: narrowOn, complete: completeOn }
804
1171
  })
805
1172
  });
806
1173
  applyConfig(value);
@@ -870,17 +1237,17 @@ ${url}`)) return;
870
1237
  setBusy(false);
871
1238
  }
872
1239
  };
873
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-config", children: [
874
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "decision-collapse", open: true, children: [
875
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("summary", { className: "decision-collapse-head", children: [
876
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { children: [
877
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HeadIcon, { name: "features" }),
1240
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-config", children: [
1241
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("details", { className: "decision-collapse", open: true, children: [
1242
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { className: "decision-collapse-head", children: [
1243
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h3", { children: [
1244
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeadIcon, { name: "features" }),
878
1245
  t("basicFeatures")
879
1246
  ] }),
880
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-collapse-icon", "aria-hidden": "true" })
1247
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "decision-collapse-icon", "aria-hidden": "true" })
881
1248
  ] }),
882
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-collapse-body", children: [
883
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1249
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-collapse-body", children: [
1250
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
884
1251
  Toggle,
885
1252
  {
886
1253
  label: t("featureGate"),
@@ -892,8 +1259,8 @@ ${url}`)) return;
892
1259
  }
893
1260
  }
894
1261
  ),
895
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("featureGateHint") }),
896
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1262
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "decision-muted", children: t("featureGateHint") }),
1263
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
897
1264
  Toggle,
898
1265
  {
899
1266
  label: t("featureCheck"),
@@ -905,8 +1272,8 @@ ${url}`)) return;
905
1272
  }
906
1273
  }
907
1274
  ),
908
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("featureCheckHint") }),
909
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1275
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "decision-muted", children: t("featureCheckHint") }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
910
1277
  Toggle,
911
1278
  {
912
1279
  label: t("featureNarrow"),
@@ -918,22 +1285,35 @@ ${url}`)) return;
918
1285
  }
919
1286
  }
920
1287
  ),
921
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("featureNarrowHint") })
1288
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "decision-muted", children: t("featureNarrowHint") }),
1289
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1290
+ Toggle,
1291
+ {
1292
+ label: t("featureComplete"),
1293
+ checked: completeOn,
1294
+ disabled: !ready,
1295
+ onChange: (next) => {
1296
+ setCompleteOn(next);
1297
+ setAdvancedDirty(true);
1298
+ }
1299
+ }
1300
+ ),
1301
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "decision-muted", children: t("featureCompleteHint") })
922
1302
  ] })
923
1303
  ] }),
924
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "decision-collapse", children: [
925
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("summary", { className: "decision-collapse-head", children: [
926
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { children: [
927
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HeadIcon, { name: "advanced" }),
1304
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("details", { className: "decision-collapse", children: [
1305
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { className: "decision-collapse-head", children: [
1306
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h3", { children: [
1307
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeadIcon, { name: "advanced" }),
928
1308
  t("advancedConfig")
929
1309
  ] }),
930
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-collapse-icon", "aria-hidden": "true" })
1310
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "decision-collapse-icon", "aria-hidden": "true" })
931
1311
  ] }),
932
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { className: "decision-collapse-body", onSubmit: (event) => void saveAdvanced(event), children: [
933
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-advanced-grid", children: [
934
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-field-block", children: [
935
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-subhead", children: t("advancedSwitches") }),
936
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1312
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { className: "decision-collapse-body", onSubmit: (event) => void saveAdvanced(event), children: [
1313
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-advanced-grid", children: [
1314
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-field-block", children: [
1315
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "decision-subhead", children: t("advancedSwitches") }),
1316
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
937
1317
  Toggle,
938
1318
  {
939
1319
  label: t("selfCheckSteer"),
@@ -945,8 +1325,8 @@ ${url}`)) return;
945
1325
  }
946
1326
  }
947
1327
  ),
948
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("selfCheckHint") }),
949
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1328
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "decision-muted", children: t("selfCheckHint") }),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
950
1330
  Toggle,
951
1331
  {
952
1332
  label: t("narrowObserveLabel"),
@@ -958,13 +1338,26 @@ ${url}`)) return;
958
1338
  }
959
1339
  }
960
1340
  ),
961
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("narrowHint") })
1341
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "decision-muted", children: t("narrowHint") }),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1343
+ Toggle,
1344
+ {
1345
+ label: t("completeSteer"),
1346
+ checked: completeMode === "steer",
1347
+ disabled: !ready || !completeOn,
1348
+ onChange: (next) => {
1349
+ setCompleteMode(next ? "steer" : "observe");
1350
+ setAdvancedDirty(true);
1351
+ }
1352
+ }
1353
+ ),
1354
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "decision-muted", children: t("completeHint") })
962
1355
  ] }),
963
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-field-block", children: [
964
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-subhead", children: t("advancedParams") }),
965
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field", children: [
1356
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-field-block", children: [
1357
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "decision-subhead", children: t("advancedParams") }),
1358
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "decision-field", children: [
966
1359
  t("narrowKeepLabel"),
967
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1360
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
968
1361
  "textarea",
969
1362
  {
970
1363
  className: "decision-prefixes",
@@ -978,11 +1371,11 @@ ${url}`)) return;
978
1371
  }
979
1372
  }
980
1373
  ),
981
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: t("narrowKeepHint") })
1374
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { className: "decision-muted", children: t("narrowKeepHint") })
982
1375
  ] }),
983
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field decision-field-inline", children: [
1376
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "decision-field decision-field-inline", children: [
984
1377
  t("narrowMaxLabel"),
985
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1378
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
986
1379
  "input",
987
1380
  {
988
1381
  className: "decision-narrow-cap",
@@ -999,11 +1392,11 @@ ${url}`)) return;
999
1392
  }
1000
1393
  }
1001
1394
  ),
1002
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: t("narrowMaxHint") })
1395
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { className: "decision-muted", children: t("narrowMaxHint") })
1003
1396
  ] }),
1004
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field decision-field-inline", children: [
1397
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "decision-field decision-field-inline", children: [
1005
1398
  t("narrowThresholdLabel"),
1006
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1399
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1007
1400
  "input",
1008
1401
  {
1009
1402
  className: "decision-narrow-cap",
@@ -1020,26 +1413,26 @@ ${url}`)) return;
1020
1413
  }
1021
1414
  }
1022
1415
  ),
1023
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: t("narrowThresholdHint") })
1416
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { className: "decision-muted", children: t("narrowThresholdHint") })
1024
1417
  ] })
1025
1418
  ] })
1026
1419
  ] }),
1027
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "decision-actions", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { disabled: busy || !ready || !advancedDirty, type: "submit", children: t("saveAdvanced") }) })
1420
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "decision-actions", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { disabled: busy || !ready || !advancedDirty, type: "submit", children: t("saveAdvanced") }) })
1028
1421
  ] })
1029
1422
  ] }),
1030
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
1031
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { children: [
1032
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HeadIcon, { name: "backend" }),
1423
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h3", { children: [
1425
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeadIcon, { name: "backend" }),
1033
1426
  t("backend")
1034
1427
  ] }),
1035
- ready && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "decision-muted decision-destination", children: [
1428
+ ready && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "decision-muted decision-destination", children: [
1036
1429
  t("destination"),
1037
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: config.effectiveUrl })
1430
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: config.effectiveUrl })
1038
1431
  ] }),
1039
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: (event) => void saveBackend(event), children: [
1040
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field", children: [
1432
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit: (event) => void saveBackend(event), children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "decision-field", children: [
1041
1434
  t("url"),
1042
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1435
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1043
1436
  "input",
1044
1437
  {
1045
1438
  type: "url",
@@ -1054,9 +1447,9 @@ ${url}`)) return;
1054
1447
  }
1055
1448
  )
1056
1449
  ] }),
1057
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field", children: [
1450
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "decision-field", children: [
1058
1451
  t("model"),
1059
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1452
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1060
1453
  "input",
1061
1454
  {
1062
1455
  value: config.model,
@@ -1070,9 +1463,9 @@ ${url}`)) return;
1070
1463
  }
1071
1464
  )
1072
1465
  ] }),
1073
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field", children: [
1466
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "decision-field", children: [
1074
1467
  t("key"),
1075
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1468
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1076
1469
  "input",
1077
1470
  {
1078
1471
  type: "password",
@@ -1087,23 +1480,23 @@ ${url}`)) return;
1087
1480
  }
1088
1481
  }
1089
1482
  ),
1090
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: config.apiKeySet ? t("keyHintSaved") : t("keyHintEmpty") })
1483
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { className: "decision-muted", children: config.apiKeySet ? t("keyHintSaved") : t("keyHintEmpty") })
1091
1484
  ] }),
1092
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-actions", children: [
1093
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { disabled: busy || !ready, type: "submit", children: t("save") }),
1094
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "decision-secondary", disabled: busy || !ready || backendDirty, type: "button", onClick: () => void probe(), children: t("probe") }),
1095
- probeResult && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { role: "status", className: `decision-probe-result${probeResult.ok ? " decision-message-ok" : " decision-message-warn"}`, children: probeResult.text })
1485
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-actions", children: [
1486
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { disabled: busy || !ready, type: "submit", children: t("save") }),
1487
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "decision-secondary", disabled: busy || !ready || backendDirty, type: "button", onClick: () => void probe(), children: t("probe") }),
1488
+ probeResult && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { role: "status", className: `decision-probe-result${probeResult.ok ? " decision-message-ok" : " decision-message-warn"}`, children: probeResult.text })
1096
1489
  ] })
1097
1490
  ] }),
1098
- config.apiKeySet && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-danger-row", children: [
1099
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-danger-text", children: [
1100
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-danger-title", children: t("removeKey") }),
1101
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: t("removeKeyHint") })
1491
+ config.apiKeySet && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-danger-row", children: [
1492
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "decision-danger-text", children: [
1493
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "decision-danger-title", children: t("removeKey") }),
1494
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { className: "decision-muted", children: t("removeKeyHint") })
1102
1495
  ] }),
1103
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "decision-danger-button", disabled: busy || !ready, onClick: () => void removeSavedKey(), children: t("removeKeyButton") })
1496
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "decision-danger-button", disabled: busy || !ready, onClick: () => void removeSavedKey(), children: t("removeKeyButton") })
1104
1497
  ] })
1105
1498
  ] }),
1106
- message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", className: `decision-message${message.ok ? " decision-message-ok" : " decision-message-warn"}`, children: message.text })
1499
+ message && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { role: "status", className: `decision-message${message.ok ? " decision-message-ok" : " decision-message-warn"}`, children: message.text })
1107
1500
  ] });
1108
1501
  }
1109
1502
 
@@ -1112,6 +1505,16 @@ var css = `
1112
1505
  .decision-trigger{position:relative;display:inline-flex;align-items:center;gap:6px;min-width:0;max-width:14rem;border:none;border-radius:var(--dsw-radius-sm,6px);background:0 0;color:var(--dsw-alias-label-tertiary,#77818b);padding:1px 8px;font:inherit;font-size:calc(var(--dsh-content-font-size-secondary,13px) - 1px);font-weight:400;line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));font-variant-numeric:tabular-nums;white-space:nowrap;cursor:pointer}
1113
1506
  .decision-trigger:hover,.decision-trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover,#eef1f3);color:var(--dsw-alias-label-secondary,#59636e)}
1114
1507
  .decision-trigger-icon{flex:none;color:inherit}
1508
+ /* Quality-trend severity on the radar-triangle icon. Only the icon is tinted, via
1509
+ a CSS variable so light/dark are one pair of overrides. Multimodal: the tooltip
1510
+ text and the icon shape carry the meaning too, not color alone. First version
1511
+ is color-only; a pulse animation is added later behind prefers-reduced-motion. */
1512
+ .decision-trend-warn .decision-trigger-icon{color:var(--decision-trend-warn,#c98a1e)}
1513
+ .decision-trend-severe .decision-trigger-icon{color:var(--decision-trend-severe,#c0392b)}
1514
+ @media (prefers-color-scheme:dark){
1515
+ .decision-trend-warn .decision-trigger-icon{color:var(--decision-trend-warn,#e0a83e)}
1516
+ .decision-trend-severe .decision-trigger-icon{color:var(--decision-trend-severe,#f0645a)}
1517
+ }
1115
1518
  .decision-trigger-text{min-width:0;flex:0 1 auto;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}
1116
1519
  .decision-trigger-text b{color:inherit;font-weight:500;font-variant-numeric:tabular-nums}
1117
1520
  .decision-trigger:focus-visible,.decision-dialog button:focus-visible,.decision-dialog input:focus-visible,.decision-config input:focus-visible,.decision-config button:focus-visible{outline:2px solid #1f5d50;outline-offset:2px}
@@ -1163,6 +1566,14 @@ var css = `
1163
1566
  .decision-log-item.decision-log-ok{border-left-color:#1f8a70}
1164
1567
  .decision-log-time{color:var(--dsw-alias-label-tertiary,#77818b);font-variant-numeric:tabular-nums;flex:0 0 auto}
1165
1568
  .decision-log-body{color:var(--dsw-alias-label-primary,#202124);flex:1 1 auto;min-width:0}
1569
+ .decision-rate{flex:0 0 auto;display:inline-flex;gap:1px;align-self:center}
1570
+ .decision-rate-btn{display:inline-flex;align-items:center;justify-content:center;width:1.55rem;height:1.55rem;border:1px solid transparent;background:0 0;border-radius:7px;padding:0;cursor:pointer;color:var(--dsw-alias-label-tertiary,#9aa5b1);transition:color .12s,background .12s}
1571
+ .decision-rate-btn:hover{color:var(--dsw-alias-label-secondary,#59636e);background:var(--dsw-alias-interactive-bg-hover,#eef1f3)}
1572
+ .decision-rate-btn.decision-rate-on{background:var(--dsw-alias-interactive-bg-hover,#eef1f3)}
1573
+ .decision-rate-btn.decision-rate-good{color:#1f8a70}
1574
+ .decision-rate-btn.decision-rate-bad{color:#c0392b}
1575
+ .decision-rate-btn.decision-rate-unsure{color:#c98a1e}
1576
+ .decision-rate-summary{margin-top:.5rem}
1166
1577
  .decision-log-tool{font-size:.76rem;padding:.05rem .35rem;border-radius:5px;background:var(--dsw-alias-bg-layer-1,#f2f4f6);color:var(--dsw-alias-label-primary,#202124)}
1167
1578
  .decision-log-verdict{font-weight:600}
1168
1579
  .decision-log-deny .decision-log-verdict{color:#c0392b}
@@ -1252,6 +1663,22 @@ var css = `
1252
1663
  .decision-message{font-size:.82rem;margin:.2rem 0 0}
1253
1664
  .decision-message-ok{color:#1f6f57}
1254
1665
  .decision-message-warn{color:#9d4b1f}
1666
+ /* Conversation-view "Decision analysis" tab. It reuses the card / stat / log
1667
+ primitives above; this block only supplies the scroll container and section
1668
+ rhythm, since the tab is not inside the modal's .decision-body wrapper. */
1669
+ .decision-analysis{height:100%;overflow:auto;color:var(--dsw-alias-label-primary,#202124);font:inherit}
1670
+ .decision-analysis-inner{max-width:52rem;margin:0 auto;padding:1.4rem 1.5rem 3rem}
1671
+ .decision-analysis-title{font-size:1.15rem;margin:0 0 .3rem}
1672
+ .decision-analysis-intro{margin:0 0 .4rem}
1673
+ .decision-analysis-section{border-top:1px solid var(--dsw-alias-border-l2,#eceff1);margin-top:1.1rem;padding-top:1rem}
1674
+ .decision-analysis-section>h3{font-size:.92rem;margin:0 0 .6rem}
1675
+ .decision-analysis-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));gap:.7rem}
1676
+ .decision-analysis-cards .decision-card-stats{flex-wrap:wrap}
1677
+ .decision-analysis-trend{max-width:26rem}
1678
+ .decision-analysis-note{margin:.5rem 0 0}
1679
+ @media (prefers-color-scheme:dark){
1680
+ .decision-analysis{color:var(--dsw-alias-label-primary,#e6eaee)}
1681
+ }
1255
1682
  `;
1256
1683
  function installStyles() {
1257
1684
  if (typeof document === "undefined" || document.querySelector('style[data-plugin="dsh-decision-layer"]')) return () => {
@@ -1264,9 +1691,10 @@ function installStyles() {
1264
1691
  }
1265
1692
 
1266
1693
  // src/client/index.tsx
1267
- var import_jsx_runtime3 = require("react/jsx-runtime");
1694
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1268
1695
  var inject = ["slots", "locale"];
1269
1696
  function apply(ctx) {
1697
+ const t = ctx.locale.bind("dsh-decision-layer");
1270
1698
  ctx.effect(installStyles);
1271
1699
  ctx.effect(() => ctx.locale.register("dsh-decision-layer", dictionaries));
1272
1700
  ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
@@ -1277,9 +1705,18 @@ function apply(ctx) {
1277
1705
  registrant: "dsh-decision-layer",
1278
1706
  inject: (sessionId) => ({ sessionId })
1279
1707
  }, DecisionPanel));
1708
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({
1709
+ name: "conversation.view",
1710
+ id: "decision-layer-analysis",
1711
+ order: 40,
1712
+ locale: "dsh-decision-layer",
1713
+ registrant: "dsh-decision-layer",
1714
+ label: () => t("analysisTab"),
1715
+ inject: (sessionId) => ({ sessionId })
1716
+ }, AnalysisView));
1280
1717
  ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register(
1281
1718
  { name: "plugins.bundle.config", key: "dsh-decision-layer", locale: "dsh-decision-layer" },
1282
- (props) => props.view === "summary" ? props.t("summary") : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConfigForm, { t: props.t })
1719
+ (props) => props.view === "summary" ? props.t("summary") : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConfigForm, { t: props.t })
1283
1720
  ));
1284
1721
  }
1285
1722