dsh-decision-layer 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1288 @@
1
+ window.__ModuleLoader__.load({
2
+ id: 'dsh-decision-layer',
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
+
24
+ // src/client/index.tsx
25
+ var index_exports = {};
26
+ __export(index_exports, {
27
+ ConfigForm: () => ConfigForm,
28
+ apply: () => apply,
29
+ inject: () => inject
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/client/i18n.ts
34
+ var dictionaries = {
35
+ zh: {
36
+ summary: "\u5371\u9669\u8C03\u7528\u95E8\u63A7\u3001\u4EA7\u51FA\u81EA\u68C0\u4E0E\u9632\u5FAA\u73AF\uFF0C\u7531\u53EF\u63D2\u62D4\u88C1\u51B3\u6A21\u578B\u9A71\u52A8\u3002",
37
+ button: "\u51B3\u7B56\u5C42",
38
+ title: "\u51B3\u7B56\u5C42",
39
+ close: "\u5173\u95ED",
40
+ metrics: "\u81EA\u52A8\u88C1\u51B3\u7EDF\u8BA1",
41
+ triggerDecisions: "\u51B3\u7B56",
42
+ empty: "\u5C1A\u65E0\u81EA\u52A8\u88C1\u51B3\u3002\u624B\u52A8\u88C1\u51B3\u4E0D\u8BA1\u5165\u8FD9\u91CC\u3002",
43
+ attempts: "\u8BC4\u4F30\u6B21\u6570",
44
+ failures: "\u5931\u8D25\u56DE\u9000",
45
+ suggestions: "\u6A21\u578B\u5EFA\u8BAE\uFF08\u653E\u884C / \u8BE2\u95EE / \u62D2\u7EDD\uFF09",
46
+ actual: "\u5B9E\u9645\u5BBF\u4E3B\u7ED3\u679C\uFF08\u653E\u884C / \u62D2\u7EDD\uFF09",
47
+ checkAttempts: "\u81EA\u68C0\u8BC4\u4F30\u6B21\u6570",
48
+ checkLow: "\u4F4E\u5206",
49
+ checkOk: "\u901A\u8FC7",
50
+ settings: "\u4F1A\u8BDD\u8BBE\u7F6E",
51
+ cardGate: "\u5371\u9669\u95E8\u63A7",
52
+ cardCheck: "\u4EA7\u51FA\u81EA\u68C0",
53
+ cardNarrow: "\u5DE5\u5177\u6536\u7A84",
54
+ cardTimes: " \u6B21",
55
+ allow: "\u653E\u884C",
56
+ ask: "\u8BE2\u95EE",
57
+ deny: "\u62D2\u7EDD",
58
+ actualShort: "\u5B9E\u9645\u7ED3\u679C",
59
+ narrowAppliedLabel: "\u6536\u7A84",
60
+ narrowDroppedLabel: "\u6458\u9664\u5DE5\u5177",
61
+ narrowDropped: "\u6458\u9664",
62
+ narrowKept: "\u672A\u6536\u7A84",
63
+ narrowKeptCount: "\u4FDD\u7559",
64
+ narrowMore: " \u7B49 {count} \u4E2A",
65
+ narrowObserve: "\u4EC5\u89C2\u5BDF",
66
+ narrowEnforce: "\u5DF2\u751F\u6548",
67
+ checkPassRate: "\u901A\u8FC7\u7387",
68
+ narrowApplyRate: "\u6536\u7A84\u7387",
69
+ rateNa: "\u2014",
70
+ enabled: "\u542F\u7528\u81EA\u52A8\u4ECB\u5165",
71
+ backendStatus: "\u540E\u7AEF\u8FDE\u63A5",
72
+ statusChecking: "\u68C0\u6D4B\u4E2D\u2026",
73
+ statusIdle: "\u672A\u68C0\u6D4B",
74
+ statusOk: "\u6B63\u5E38",
75
+ statusDown: "\u4E0D\u53EF\u7528",
76
+ recheck: "\u91CD\u65B0\u68C0\u6D4B",
77
+ log: "\u51B3\u7B56\u65E5\u5FD7",
78
+ logEmpty: "\u672C\u4F1A\u8BDD\u5C1A\u65E0\u51B3\u7B56\u8BB0\u5F55\u3002",
79
+ logGate: "\u95E8\u63A7",
80
+ logCheck: "\u81EA\u68C0",
81
+ logNarrow: "\u6536\u7A84",
82
+ logSuggested: "\u6A21\u578B\u5EFA\u8BAE",
83
+ logEvalFailed: "\u8BC4\u4F30\u5931\u8D25",
84
+ logScore: "\u5F97\u5206",
85
+ logConnection: "\u8FDE\u63A5",
86
+ logConfidence: "\u7F6E\u4FE1\u5EA6",
87
+ logScoreHint: "\u5F97\u5206\u4E3A\u5404\u6863\u4F4D\u6309\u6982\u7387\u52A0\u6743\u7684\u7ED3\u679C\uFF0C\u53EF\u80FD\u662F\u5C0F\u6570\uFF08\u5982 1.4\uFF09\uFF0C\u6EE1\u5206\u4E3A\u9876\u6863\u53F7 2\u3002",
88
+ logCheckHint: "\u6BCF\u56DE\u5408\u7ED3\u675F\u540E\uFF0C\u628A\u8FD9\u8F6E AI \u7684\u6700\u7EC8\u56DE\u590D\u4EA4\u7ED9\u88C1\u51B3\u6A21\u578B\uFF0C\u6309\u300C\u4E25\u91CD\u9057\u6F0F/\u81EA\u76F8\u77DB\u76FE \u2192 \u6709\u5C0F\u9057\u6F0F \u2192 \u5B8C\u6574\u51C6\u786E\u300D\u4E09\u6863\u6253\u4E00\u4E2A 0\u20132 \u7684\u8D28\u91CF\u5206\uFF0C\u7528\u6765\u53D1\u73B0\u9057\u6F0F\u6216\u77DB\u76FE\u3002",
89
+ 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
+ actionDeny: "\u5DF2\u62D2\u7EDD",
91
+ actionAsk: "\u8F6C\u4EA4\u5BA1\u6279",
92
+ checkResultLow: "\u5F97\u5206\u504F\u4F4E",
93
+ checkResultOk: "\u901A\u8FC7",
94
+ reasonUnavailable: "\u540E\u7AEF\u4E0D\u53EF\u8FBE",
95
+ reasonHttpError: "\u540E\u7AEF\u8FD4\u56DE\u9519\u8BEF",
96
+ reasonInvalidResponse: "\u54CD\u5E94\u683C\u5F0F\u4E0D\u7B26",
97
+ reasonLowConfidence: "\u7F6E\u4FE1\u5EA6\u4E0D\u8DB3",
98
+ reasonCapacity: "\u4F1A\u8BDD\u72B6\u6001\u5DF2\u6EE1",
99
+ reasonConfig: "\u914D\u7F6E\u65E0\u6548",
100
+ reasonTooMany: "\u5019\u9009\u8FC7\u591A",
101
+ reasonTooManyCount: "\u5019\u9009 {count} \u4E2A\u504F\u591A",
102
+ reasonLowKeep: "\u4FDD\u7559\u8FC7\u5C11\uFF0C\u672A\u751F\u6548",
103
+ selfCheck: "\u4EA7\u51FA\u81EA\u68C0",
104
+ advancedSwitches: "\u4ECB\u5165\u5F00\u5173",
105
+ advancedParams: "\u6536\u7A84\u53C2\u6570",
106
+ selfCheckSteer: "\u5F97\u5206\u504F\u4F4E\u65F6\u81EA\u52A8\u8865\u4E00\u8F6E",
107
+ selfCheckHint: "\u6BCF\u56DE\u5408\u7ED3\u675F\u7ED9\u6700\u7EC8\u56DE\u590D\u6253\u5206\uFF080\u20132\uFF0C\u6309\u5404\u6863\u4F4D\u6982\u7387\u52A0\u6743\uFF0C\u53EF\u80FD\u662F\u5C0F\u6570\u5982 1.4\uFF09\u3002\u5173\u95ED\uFF1A\u53EA\u628A\u4F4E\u5206\u8BB0\u8FDB\u65E5\u5FD7\u548C\u7EDF\u8BA1\uFF0C\u4E0D\u6253\u65AD\u5BF9\u8BDD\u3002\u5F00\u542F\uFF1A\u5F97\u5206\u504F\u4F4E\uFF08\u22641\uFF09\u4E14\u6A21\u578B\u6709\u628A\u63E1\u65F6\uFF0C\u81EA\u52A8\u4EE5\u4F60\u7684\u540D\u4E49\u8FFD\u52A0\u4E00\u6761\u63D0\u793A\uFF0C\u8BA9 AI \u590D\u6838\u6709\u65E0\u9057\u6F0F\u6216\u77DB\u76FE\u5E76\u8865\u5B8C\uFF1B\u7F6E\u4FE1\u5EA6\u504F\u4F4E\u7684\u6253\u5206\u53EA\u8BB0\u5F55\u3001\u4E0D\u6253\u65AD\uFF0C\u540C\u4E00\u56DE\u5408\u6700\u591A\u8865\u4E00\u6B21\u3002",
108
+ narrow: "\u5DE5\u5177\u6536\u7A84",
109
+ narrowObserveLabel: "\u4EC5\u89C2\u5BDF\uFF08\u4E0D\u771F\u6B63\u9650\u5236\u5DE5\u5177\uFF09",
110
+ narrowHint: "\u6BCF\u56DE\u5408\u5F00\u59CB\u65F6\uFF0C\u7528\u88C1\u51B3\u6A21\u578B\u5224\u65AD\u5F53\u524D\u53EF\u9009\u5DE5\u5177\u4E0E\u8FD9\u8F6E\u4EFB\u52A1\u7684\u76F8\u5173\u6027\uFF0C\u6458\u6389\u7528\u4E0D\u4E0A\u7684\uFF1B\u8BFB\u5199\u3001\u7F16\u8F91\u3001Shell\u3001\u68C0\u7D22\u3001\u5F85\u529E\u7B49\u6838\u5FC3\u5DE5\u5177\u59CB\u7EC8\u4FDD\u7559\u3001\u4E0D\u53C2\u4E0E\u5224\u5B9A\u3002\u9ED8\u8BA4\u771F\u6B63\u751F\u6548\uFF1A\u672C\u56DE\u5408\u53EA\u4FDD\u7559\u5224\u4E3A\u76F8\u5173\u7684\u5DE5\u5177\uFF0C\u4E14\u53EA\u5728\u5BBF\u4E3B\u5DF2\u5141\u8BB8\u7684\u8303\u56F4\u5185\u53D6\u4EA4\u96C6\u3001\u4E0D\u6269\u6743\u3002\u5F00\u542F\u300C\u4EC5\u89C2\u5BDF\u300D\u540E\u53EA\u628A\u53EF\u6458\u6389\u7684\u5DE5\u5177\u8BB0\u8FDB\u65E5\u5FD7\u548C\u7EDF\u8BA1\uFF0C\u4E0D\u6539\u52A8\u5DE5\u5177\u96C6\u3002\u4E09\u91CD\u4FDD\u62A4\uFF1A\u6838\u5FC3\u5DE5\u5177\u59CB\u7EC8\u4FDD\u7559\uFF1B\u53EF\u9009\u5DE5\u5177\u8D85\u8FC7 20 \u4E2A\u65F6\u81EA\u52A8\u53EA\u89C2\u5BDF\u4E0D\u9650\u5236\uFF1B\u4FDD\u7559\u4E0B\u6765\u4E0D\u8DB3 5 \u4E2A\u65F6\u89C6\u4E3A\u5224\u5B9A\u4E0D\u53EF\u4FE1\u3001\u8DF3\u8FC7\u3002",
111
+ narrowKeepLabel: "\u59CB\u7EC8\u4FDD\u7559\u7684\u5DE5\u5177\u524D\u7F00",
112
+ narrowKeepHint: "\u540D\u5B57\u4EE5\u8FD9\u4E9B\u524D\u7F00\u5F00\u5934\u7684\u5DE5\u5177\u6C38\u8FDC\u4FDD\u7559\u3001\u4E0D\u53C2\u4E0E\u76F8\u5173\u6027\u5224\u5B9A\uFF0C\u9002\u5408\u8BB0\u5FC6/\u68C0\u7D22\u8FD9\u7C7B\u300C\u5E38\u9A7B\u3001\u6309\u9700\u89E6\u53D1\u300D\u7684\u5DE5\u5177\uFF08\u9ED8\u8BA4\u542B mcp__openviking\u3001team_task_\uFF09\u3002\u7528\u82F1\u6587\u9017\u53F7\u6216\u6362\u884C\u5206\u9694\u591A\u4E2A\u524D\u7F00\uFF1B\u7559\u7A7A\u5219\u4E0D\u989D\u5916\u4FDD\u7559\u3002",
113
+ narrowMaxLabel: "\u5019\u9009\u5DE5\u5177\u4E0A\u9650",
114
+ narrowMaxHint: "\u4E00\u56DE\u5408\u91CC\u53C2\u4E0E\u5224\u5B9A\u7684\u300C\u53EF\u9009\u5DE5\u5177\u300D\u8D85\u8FC7\u8FD9\u4E2A\u6570\uFF08\u9ED8\u8BA4 20\uFF09\uFF0C\u5C31\u81EA\u52A8\u964D\u7EA7\u4E3A\u4EC5\u89C2\u5BDF\u3001\u4E0D\u771F\u6B63\u6536\u7A84\uFF0C\u907F\u514D\u4E00\u6B21\u585E\u592A\u591A\u5DE5\u5177\u8BA9\u6A21\u578B\u8BEF\u6458\u3002\u53D6\u503C 1\u2013200\uFF0C\u8C03\u5927\u66F4\u503E\u5411\u771F\u6B63\u6536\u7A84\u3002\u6838\u5FC3\u5DE5\u5177\u4E0E\u4FDD\u7559\u524D\u7F00\u547D\u4E2D\u7684\u5DE5\u5177\u4E0D\u8BA1\u5165\u3002",
115
+ narrowThresholdLabel: "\u6458\u9664\u9608\u503C",
116
+ narrowThresholdHint: "\u76F8\u5173\u6027\u6982\u7387\u4F4E\u4E8E\u8FD9\u4E2A\u503C\u624D\u6458\u6389\u5DE5\u5177\uFF08\u9ED8\u8BA4 0.3\uFF09\u3002\u6982\u7387\u63A5\u8FD1 0.5 \u8868\u793A\u6A21\u578B\u62FF\u4E0D\u51C6\u3001\u5E76\u975E\u300C\u4E2D\u7B49\u76F8\u5173\u300D\uFF0C\u6B64\u65F6\u4FDD\u7559\u66F4\u7A33\u59A5\uFF1B\u8C03\u4F4E\u66F4\u4FDD\u5B88\uFF08\u5C11\u6458\u3001\u5B81\u53EF\u591A\u7559\uFF09\uFF0C\u8C03\u9AD8\u66F4\u6FC0\u8FDB\uFF08\u591A\u6458\uFF09\u3002\u53D6\u503C 0\u20131\u3002",
117
+ features: "\u51B3\u7B56\u70B9\u5F00\u5173",
118
+ basicFeatures: "\u57FA\u7840\u529F\u80FD",
119
+ advancedConfig: "\u9AD8\u7EA7\u914D\u7F6E",
120
+ featureGate: "\u5371\u9669\u95E8\u63A7",
121
+ featureGateHint: "\u5173\u95ED\u540E\u4E0D\u518D\u5BF9\u7834\u574F\u6027\u5DE5\u5177\u8C03\u7528\u505A\u62E6\u622A\u8BC4\u4F30\uFF0C\u8C03\u7528\u53EA\u53D7\u5BBF\u4E3B\u539F\u751F\u6743\u9650\u7EA6\u675F\u3002",
122
+ featureCheck: "\u4EA7\u51FA\u81EA\u68C0",
123
+ featureCheckHint: "\u5173\u95ED\u540E\u4E0D\u518D\u5728\u56DE\u5408\u7ED3\u675F\u65F6\u7ED9\u6700\u7EC8\u56DE\u590D\u6253\u5206\u3002",
124
+ featureNarrow: "\u5DE5\u5177\u6536\u7A84",
125
+ 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",
126
+ backend: "\u88C1\u51B3\u540E\u7AEF",
127
+ url: "\u670D\u52A1\u5730\u5740",
128
+ model: "\u6A21\u578B",
129
+ key: "API Key",
130
+ keyHintSaved: "\u5DF2\u4FDD\u5B58\u4E00\u4E2A Key\u3002\u7559\u7A7A\u5E76\u4FDD\u5B58\uFF1D\u6CBF\u7528\u5B83\uFF08\u4E0D\u6539\u52A8\uFF09\uFF1B\u586B\u65B0\u503C\u5E76\u4FDD\u5B58\uFF1D\u8986\u76D6\u5B83\u3002",
131
+ keyHintEmpty: "\u5C1A\u672A\u4FDD\u5B58 Key\u3002\u7559\u7A7A\u5219\u4F7F\u7528\u73AF\u5883\u53D8\u91CF\u91CC\u7684 Key\uFF1B\u586B\u5165\u5219\u4FDD\u5B58\u5230\u672C\u673A\u3002",
132
+ removeKey: "\u5220\u9664\u5DF2\u4FDD\u5B58\u7684 Key",
133
+ removeKeyHint: "\u628A\u672C\u63D2\u4EF6\u4FDD\u5B58\u5728\u672C\u673A\u7684 Key \u6C38\u4E45\u5220\u9664\uFF0C\u4E4B\u540E\u81EA\u52A8\u56DE\u843D\u5230\u73AF\u5883\u53D8\u91CF\u91CC\u7684 Key\uFF1B\u4E0D\u5F71\u54CD\u4F60\u5728\u522B\u5904\u914D\u7F6E\u7684\u51ED\u636E\u3002",
134
+ removeKeyButton: "\u5220\u9664",
135
+ removeKeyConfirm: "\u786E\u5B9A\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684 API Key\uFF1F\u5220\u9664\u540E\u5C06\u56DE\u843D\u5230\u73AF\u5883\u53D8\u91CF\u7684 Key\u3002",
136
+ removeKeyDone: "\u5DF2\u5220\u9664\u4FDD\u5B58\u7684 Key",
137
+ save: "\u4FDD\u5B58\u914D\u7F6E",
138
+ saveAdvanced: "\u4FDD\u5B58\u9AD8\u7EA7\u914D\u7F6E",
139
+ probe: "\u6D4B\u8BD5\u8FDE\u63A5",
140
+ saved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58",
141
+ connected: "\u8FDE\u63A5\u6210\u529F",
142
+ unavailable: "\u8FDE\u63A5\u4E0D\u53EF\u7528",
143
+ error: "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002",
144
+ 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
+ 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"
148
+ },
149
+ en: {
150
+ summary: "A danger-call gate, output self-check, and loop guard driven by a pluggable adjudication model.",
151
+ button: "Decision layer",
152
+ title: "Decision layer",
153
+ close: "Close",
154
+ metrics: "Automatic decisions",
155
+ triggerDecisions: "decisions",
156
+ empty: "No automatic decisions yet. Manual verdicts are not counted here.",
157
+ attempts: "Evaluations",
158
+ failures: "Fallback failures",
159
+ suggestions: "Model suggestions (allow / ask / deny)",
160
+ actual: "Actual host outcomes (allow / deny)",
161
+ checkAttempts: "Self-check evaluations",
162
+ checkLow: "Low",
163
+ checkOk: "Passed",
164
+ settings: "Session settings",
165
+ cardGate: "Danger gate",
166
+ cardCheck: "Output self-check",
167
+ cardNarrow: "Tool narrowing",
168
+ cardTimes: "",
169
+ allow: "Allow",
170
+ ask: "Ask",
171
+ deny: "Deny",
172
+ actualShort: "Actual",
173
+ narrowAppliedLabel: "Narrowed",
174
+ narrowDroppedLabel: "Tools dropped",
175
+ narrowDropped: "dropped",
176
+ narrowKept: "kept all",
177
+ narrowKeptCount: "kept",
178
+ narrowMore: " and {count} in total",
179
+ narrowObserve: "observe",
180
+ narrowEnforce: "enforced",
181
+ checkPassRate: "Pass rate",
182
+ narrowApplyRate: "Narrow rate",
183
+ rateNa: "\u2014",
184
+ enabled: "Enable automatic interventions",
185
+ backendStatus: "Backend",
186
+ statusChecking: "Checking\u2026",
187
+ statusIdle: "Not checked",
188
+ statusOk: "Connected",
189
+ statusDown: "Unavailable",
190
+ recheck: "Recheck",
191
+ log: "Decision log",
192
+ logEmpty: "No decisions recorded for this session yet.",
193
+ logGate: "Gate",
194
+ logCheck: "Self-check",
195
+ logNarrow: "Narrow",
196
+ logSuggested: "model suggested",
197
+ logEvalFailed: "evaluation failed",
198
+ logScore: "score",
199
+ logConnection: "connection",
200
+ logConfidence: "confidence",
201
+ logScoreHint: "The score is a probability-weighted mean across the levels, so it can be fractional (e.g. 1.4); the top score is the highest level number, 2.",
202
+ logCheckHint: "After each turn, the AI\u2019s final reply for that turn is sent to the adjudication model, which grades its quality on three levels (severe omission/contradiction \u2192 minor gaps \u2192 complete and accurate) as a 0\u20132 score, to catch omissions or contradictions.",
203
+ 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
+ actionDeny: "denied",
205
+ actionAsk: "sent to approval",
206
+ checkResultLow: "low score",
207
+ checkResultOk: "passed",
208
+ reasonUnavailable: "backend unreachable",
209
+ reasonHttpError: "backend returned an error",
210
+ reasonInvalidResponse: "unexpected response shape",
211
+ reasonLowConfidence: "low confidence",
212
+ reasonCapacity: "session state full",
213
+ reasonConfig: "invalid configuration",
214
+ reasonTooMany: "too many candidates",
215
+ reasonTooManyCount: "{count} candidates is too many",
216
+ reasonLowKeep: "too few kept, not enforced",
217
+ selfCheck: "Output self-check",
218
+ advancedSwitches: "Intervention switches",
219
+ advancedParams: "Narrowing parameters",
220
+ selfCheckSteer: "Auto re-prompt on a low score",
221
+ selfCheckHint: "Each turn ends by scoring the final reply (0\u20132, a probability-weighted mean across the levels, so it can be fractional like 1.4). Off: low scores are only logged, the conversation is not interrupted. On: when the score is low (\u22641) and the model is confident enough, one prompt is appended on your behalf asking the AI to review for gaps or contradictions and finish the work; low-confidence scores are only logged, never interrupt, and at most one prompt per turn.",
222
+ narrow: "Tool narrowing",
223
+ narrowObserveLabel: "Observe only (do not restrict tools)",
224
+ narrowHint: "At the start of each turn, the adjudication model judges how relevant each optional tool is to the task and drops the ones not needed; core tools (read, write, edit, shell, search, todos) are always kept and never judged. Enforced by default: only the relevant tools are kept for this turn, intersected with the host-allowed set and never expanded. Turn on Observe only to just log the tools that could be dropped, without changing the tool set. Three safeguards: core tools always kept; more than 20 optional tools falls back to observe only; fewer than 5 tools remaining is treated as untrustworthy and skipped.",
225
+ narrowKeepLabel: "Always-kept tool prefixes",
226
+ narrowKeepHint: "Tools whose names start with any of these prefixes are always kept and never judged for relevance \u2014 good for resident, on-demand tools like memory/retrieval (defaults to mcp__openviking and team_task_). Separate prefixes with commas or newlines; leave empty to keep nothing extra.",
227
+ narrowMaxLabel: "Candidate tool cap",
228
+ narrowMaxHint: "When more than this many optional tools are judged in one turn (default 20), narrowing downgrades to observe only instead of actually restricting, so a large surface is never mass-pruned. Range 1\u2013200; a higher value leans toward actually narrowing. Core tools and keep-prefix matches do not count.",
229
+ narrowThresholdLabel: "Drop threshold",
230
+ narrowThresholdHint: 'A tool is dropped only when its relevance probability is below this value (default 0.3). A probability near 0.5 means the model is unsure, not "moderately relevant", so keeping is safer there; lower is more conservative (drops fewer), higher is more aggressive. Range 0\u20131.',
231
+ features: "Decision points",
232
+ basicFeatures: "Basic features",
233
+ advancedConfig: "Advanced settings",
234
+ featureGate: "Danger gate",
235
+ featureGateHint: "When off, destructive tool calls are no longer evaluated for interception and are only bound by the host permission rules.",
236
+ featureCheck: "Output self-check",
237
+ featureCheckHint: "When off, the final reply is no longer scored at the end of a turn.",
238
+ featureNarrow: "Tool narrowing",
239
+ 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.",
240
+ backend: "Decision backend",
241
+ url: "Server URL",
242
+ model: "Model",
243
+ key: "API key",
244
+ keyHintSaved: "A key is saved. Leave blank and save to keep it; enter a new value and save to overwrite it.",
245
+ keyHintEmpty: "No key saved yet. Leave blank to use an environment credential, or enter one to save it locally.",
246
+ removeKey: "Delete saved API key",
247
+ removeKeyHint: "Permanently deletes the key this plugin stored locally, then falls back to the environment credential; credentials configured elsewhere are untouched.",
248
+ removeKeyButton: "Delete",
249
+ removeKeyConfirm: "Delete the locally saved API key? It will fall back to the environment credential.",
250
+ removeKeyDone: "Saved key deleted",
251
+ save: "Save configuration",
252
+ saveAdvanced: "Save advanced settings",
253
+ probe: "Test connection",
254
+ saved: "Configuration saved",
255
+ connected: "Connected",
256
+ unavailable: "Connection unavailable",
257
+ error: "Request failed. Please retry.",
258
+ loading: "Loading session settings",
259
+ capacity: "Session capacity reached: automatic intervention is paused here; host permission rules still apply.",
260
+ 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."
262
+ }
263
+ };
264
+
265
+ // 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";
269
+ async function request(path, init) {
270
+ const response = await fetch(`${api}/${path}`, init);
271
+ const body = await response.json();
272
+ if (!response.ok || !body.ok || body.value === void 0) throw new Error(body.error || "Request failed");
273
+ return body.value;
274
+ }
275
+ function DecisionPanel(props) {
276
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SessionPanel, { ...props }, props.sessionId);
277
+ }
278
+ function Bar({ allow, ask, deny }) {
279
+ 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" });
281
+ 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) } })
286
+ ] });
287
+ }
288
+ 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)(() => {
300
+ let live = true;
301
+ void request(`session?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
302
+ if (live) {
303
+ setEnabled(value.enabled);
304
+ setCapacityExceeded(Boolean(value.capacityExceeded));
305
+ }
306
+ }).catch(() => {
307
+ if (live) setMessage(t("error"));
308
+ });
309
+ void request(`metrics?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
310
+ if (live) setMetrics(value);
311
+ }).catch(() => {
312
+ });
313
+ return () => {
314
+ live = false;
315
+ };
316
+ }, [sessionId, t]);
317
+ const checkStatus = (live) => {
318
+ setStatus("checking");
319
+ void request("probe", { method: "POST" }).then((value) => {
320
+ if (!live()) return;
321
+ setStatus(value.connected ? "ok" : "down");
322
+ }).catch(() => {
323
+ if (live()) setStatus("down");
324
+ });
325
+ };
326
+ (0, import_react.useEffect)(() => {
327
+ if (!open) return;
328
+ const element = dialog.current;
329
+ if (!element) return;
330
+ if (!element.open) element.showModal();
331
+ let alive = true;
332
+ setMessage("");
333
+ void request(`metrics?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
334
+ if (alive) setMetrics(value);
335
+ }).catch(() => {
336
+ });
337
+ void request(`log?sessionId=${encodeURIComponent(sessionId)}`).then((value) => {
338
+ if (alive) setLog(value.entries);
339
+ }).catch(() => {
340
+ });
341
+ return () => {
342
+ alive = false;
343
+ element.close();
344
+ };
345
+ }, [open, t, sessionId]);
346
+ const updateEnabled = async (next) => {
347
+ setBusy(true);
348
+ try {
349
+ const value = await request("session", {
350
+ method: "PUT",
351
+ headers: { "content-type": "application/json" },
352
+ body: JSON.stringify({ sessionId, enabled: next })
353
+ });
354
+ setEnabled(value.enabled);
355
+ setCapacityExceeded(Boolean(value.capacityExceeded));
356
+ } catch {
357
+ setMessage(t("error"));
358
+ } finally {
359
+ setBusy(false);
360
+ }
361
+ };
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);
489
+ const statusLabel = status === "ok" ? t("statusOk") : status === "down" ? t("statusDown") : status === "checking" ? t("statusChecking") : t("statusIdle");
490
+ const attempts = metrics?.attempts ?? 0;
491
+ const checkValid = metrics?.check ? metrics.check.attempts - metrics.check.failures : 0;
492
+ const checkPassRate = metrics?.check && checkValid > 0 ? Math.round((checkValid - metrics.check.low) / checkValid * 100) : null;
493
+ const narrowValid = metrics?.narrow ? metrics.narrow.attempts - metrics.narrow.failures : 0;
494
+ 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" })
500
+ ] }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "decision-trigger-text", children: [
502
+ t("triggerDecisions"),
503
+ " ",
504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: attempts })
505
+ ] })
506
+ ] }),
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
508
+ "dialog",
509
+ {
510
+ ref: dialog,
511
+ className: "decision-dialog",
512
+ "aria-labelledby": "decision-panel-title",
513
+ onKeyDown: (event) => {
514
+ if (event.key === "Escape") {
515
+ event.stopPropagation();
516
+ event.preventDefault();
517
+ dialog.current?.close();
518
+ }
519
+ },
520
+ onCancel: (event) => {
521
+ event.preventDefault();
522
+ dialog.current?.close();
523
+ },
524
+ onClick: (event) => {
525
+ if (event.target === dialog.current) dialog.current?.close();
526
+ },
527
+ onClose: () => {
528
+ setOpen(false);
529
+ trigger.current?.focus();
530
+ },
531
+ 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: () => {
535
+ dialog.current?.close();
536
+ }, "aria-label": t("close"), children: "\xD7" })
537
+ ] }),
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)(
546
+ "input",
547
+ {
548
+ className: "decision-toggle-input",
549
+ type: "checkbox",
550
+ checked: enabled === true,
551
+ disabled: busy || enabled === null || capacityExceeded,
552
+ onChange: (event) => void updateEnabled(event.target.checked)
553
+ }
554
+ ),
555
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "decision-toggle-track", "aria-hidden": "true" })
556
+ ] })
557
+ ] }),
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: [
561
+ t("backendStatus"),
562
+ "\uFF1A",
563
+ statusLabel
564
+ ] }),
565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "decision-status-recheck", disabled: status === "checking", onClick: () => checkStatus(() => true), children: t("recheck") })
566
+ ] })
567
+ ] }),
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: [
575
+ metrics.gate.attempts,
576
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("cardTimes") })
577
+ ] })
578
+ ] }),
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" }),
583
+ t("allow"),
584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.allow })
585
+ ] }),
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" }),
588
+ t("ask"),
589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.ask })
590
+ ] }),
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" }),
593
+ t("deny"),
594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.deny })
595
+ ] })
596
+ ] }),
597
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "decision-card-foot", children: [
598
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
599
+ t("actualShort"),
600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("b", { children: [
601
+ metrics.gate.actual.allow,
602
+ " / ",
603
+ metrics.gate.actual.deny
604
+ ] })
605
+ ] }),
606
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
607
+ t("failures"),
608
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: metrics.gate.failures })
609
+ ] })
610
+ ] })
611
+ ] }),
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: [
616
+ metrics.check.attempts,
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("cardTimes") })
618
+ ] })
619
+ ] }),
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") })
624
+ ] }),
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") })
628
+ ] }),
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") })
632
+ ] })
633
+ ] }),
634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("footer", { className: "decision-card-foot decision-card-rate", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
635
+ t("checkPassRate"),
636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: checkPassRate === null ? t("rateNa") : `${checkPassRate}%` })
637
+ ] }) })
638
+ ] }),
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: [
643
+ metrics.narrow.attempts,
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t("cardTimes") })
645
+ ] })
646
+ ] }),
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") })
651
+ ] }),
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") })
655
+ ] }),
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") })
659
+ ] })
660
+ ] }),
661
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("footer", { className: "decision-card-foot decision-card-rate", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
662
+ t("narrowApplyRate"),
663
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: narrowApplyRate === null ? t("rateNa") : `${narrowApplyRate}%` })
664
+ ] }) })
665
+ ] })
666
+ ] })
667
+ ] }),
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}`)) })
674
+ ] }),
675
+ message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: "decision-message decision-message-warn", children: message })
676
+ ] })
677
+ ]
678
+ }
679
+ )
680
+ ] });
681
+ }
682
+
683
+ // 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";
687
+ async function request2(path, fetchFn, init) {
688
+ const response = await fetchFn(`${api2}/${path}`, init);
689
+ const body = await response.json();
690
+ if (!response.ok || !body.ok || body.value === void 0) throw new Error(body.error || "Request failed");
691
+ return body.value;
692
+ }
693
+ var DEFAULT_KEEP_PREFIXES = ["mcp__openviking", "team_task_"];
694
+ var DEFAULT_MAX_CANDIDATES = 20;
695
+ var DEFAULT_THRESHOLD = 0.3;
696
+ var parsePrefixes = (text) => Array.from(new Set(text.split(/[,\r\n]+/).map((line) => line.trim()).filter(Boolean)));
697
+ var prefixesText = (settings) => (settings?.keepPrefixes ?? DEFAULT_KEEP_PREFIXES).join("\n");
698
+ var maxCandidatesOf = (settings) => settings?.maxCandidates ?? DEFAULT_MAX_CANDIDATES;
699
+ var thresholdOf = (settings) => settings?.threshold ?? DEFAULT_THRESHOLD;
700
+ function HeadIcon({ name }) {
701
+ 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" })
707
+ ] }),
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" })
712
+ ] }),
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" })
718
+ ] })
719
+ };
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] }) });
721
+ }
722
+ 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)(
727
+ "input",
728
+ {
729
+ className: "decision-toggle-input",
730
+ type: "checkbox",
731
+ checked,
732
+ disabled,
733
+ onChange: (event) => onChange(event.target.checked)
734
+ }
735
+ ),
736
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-toggle-track", "aria-hidden": "true" })
737
+ ] })
738
+ ] });
739
+ }
740
+ 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);
757
+ const applyFeatures = (features) => {
758
+ setGateOn(features?.gate !== false);
759
+ setCheckOn(features?.check !== false);
760
+ setNarrowOn(features?.narrow !== false);
761
+ };
762
+ const applyConfig = (value) => {
763
+ setConfig(value);
764
+ setCheckMode(value.checkSettings?.mode ?? "observe");
765
+ setNarrowMode(value.narrowSettings?.mode ?? "enforce");
766
+ setKeepPrefixes(prefixesText(value.narrowSettings));
767
+ setMaxCandidates(String(maxCandidatesOf(value.narrowSettings)));
768
+ setThreshold(String(thresholdOf(value.narrowSettings)));
769
+ applyFeatures(value.features);
770
+ };
771
+ (0, import_react2.useEffect)(() => {
772
+ let live = true;
773
+ setMessage(null);
774
+ setReady(false);
775
+ void request2("config", fetchFn).then((value) => {
776
+ if (live) {
777
+ applyConfig(value);
778
+ setAdvancedDirty(false);
779
+ setBackendDirty(false);
780
+ setReady(true);
781
+ }
782
+ }).catch(() => {
783
+ if (live) setMessage({ text: t("error"), ok: false });
784
+ });
785
+ return () => {
786
+ live = false;
787
+ };
788
+ }, [t, fetchFn]);
789
+ const saveAdvanced = async (event) => {
790
+ event.preventDefault();
791
+ const parsedCap = Number.parseInt(maxCandidates, 10);
792
+ const cap = Number.isFinite(parsedCap) ? Math.min(200, Math.max(1, parsedCap)) : DEFAULT_MAX_CANDIDATES;
793
+ const parsedThreshold = Number.parseFloat(threshold);
794
+ const thresh = Number.isFinite(parsedThreshold) ? Math.min(1, Math.max(0, parsedThreshold)) : DEFAULT_THRESHOLD;
795
+ setBusy(true);
796
+ try {
797
+ const value = await request2("config", fetchFn, {
798
+ method: "PUT",
799
+ headers: { "content-type": "application/json" },
800
+ body: JSON.stringify({
801
+ checkSettings: { mode: checkMode },
802
+ narrowSettings: { mode: narrowMode, keepPrefixes: parsePrefixes(keepPrefixes), maxCandidates: cap, threshold: thresh },
803
+ features: { gate: gateOn, check: checkOn, narrow: narrowOn }
804
+ })
805
+ });
806
+ applyConfig(value);
807
+ setAdvancedDirty(false);
808
+ setMessage({ text: t("saved"), ok: true });
809
+ } catch {
810
+ setMessage({ text: t("error"), ok: false });
811
+ } finally {
812
+ setBusy(false);
813
+ }
814
+ };
815
+ const saveBackend = async (event) => {
816
+ event.preventDefault();
817
+ const url = config.url.trim();
818
+ const needsHttpConsent = url.startsWith("http://") && url !== config.httpApprovedUrl;
819
+ if (needsHttpConsent && !window.confirm(`${t("httpWarning")}
820
+ ${url}`)) return;
821
+ setBusy(true);
822
+ try {
823
+ const value = await request2("config", fetchFn, {
824
+ method: "PUT",
825
+ headers: { "content-type": "application/json" },
826
+ body: JSON.stringify({
827
+ url,
828
+ model: config.model,
829
+ ...key ? { apiKey: key } : {},
830
+ ...needsHttpConsent ? { confirmHttpUrl: url } : {}
831
+ })
832
+ });
833
+ applyConfig(value);
834
+ setKey("");
835
+ setBackendDirty(false);
836
+ setMessage({ text: t("saved"), ok: true });
837
+ } catch {
838
+ setMessage({ text: t("error"), ok: false });
839
+ } finally {
840
+ setBusy(false);
841
+ }
842
+ };
843
+ const probe = async () => {
844
+ setBusy(true);
845
+ setProbeResult(null);
846
+ try {
847
+ const value = await request2("probe", fetchFn, { method: "POST" });
848
+ setProbeResult({ text: t(value.connected ? "connected" : "unavailable"), ok: value.connected });
849
+ } catch {
850
+ setProbeResult({ text: t("error"), ok: false });
851
+ } finally {
852
+ setBusy(false);
853
+ }
854
+ };
855
+ const removeSavedKey = async () => {
856
+ if (!window.confirm(t("removeKeyConfirm"))) return;
857
+ setBusy(true);
858
+ try {
859
+ const value = await request2("config", fetchFn, {
860
+ method: "PUT",
861
+ headers: { "content-type": "application/json" },
862
+ body: JSON.stringify({ apiKey: "" })
863
+ });
864
+ setConfig(value);
865
+ setKey("");
866
+ setMessage({ text: t("removeKeyDone"), ok: true });
867
+ } catch {
868
+ setMessage({ text: t("error"), ok: false });
869
+ } finally {
870
+ setBusy(false);
871
+ }
872
+ };
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" }),
878
+ t("basicFeatures")
879
+ ] }),
880
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-collapse-icon", "aria-hidden": "true" })
881
+ ] }),
882
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "decision-collapse-body", children: [
883
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
884
+ Toggle,
885
+ {
886
+ label: t("featureGate"),
887
+ checked: gateOn,
888
+ disabled: !ready,
889
+ onChange: (next) => {
890
+ setGateOn(next);
891
+ setAdvancedDirty(true);
892
+ }
893
+ }
894
+ ),
895
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("featureGateHint") }),
896
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
897
+ Toggle,
898
+ {
899
+ label: t("featureCheck"),
900
+ checked: checkOn,
901
+ disabled: !ready,
902
+ onChange: (next) => {
903
+ setCheckOn(next);
904
+ setAdvancedDirty(true);
905
+ }
906
+ }
907
+ ),
908
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("featureCheckHint") }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
910
+ Toggle,
911
+ {
912
+ label: t("featureNarrow"),
913
+ checked: narrowOn,
914
+ disabled: !ready,
915
+ onChange: (next) => {
916
+ setNarrowOn(next);
917
+ setAdvancedDirty(true);
918
+ }
919
+ }
920
+ ),
921
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("featureNarrowHint") })
922
+ ] })
923
+ ] }),
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" }),
928
+ t("advancedConfig")
929
+ ] }),
930
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "decision-collapse-icon", "aria-hidden": "true" })
931
+ ] }),
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)(
937
+ Toggle,
938
+ {
939
+ label: t("selfCheckSteer"),
940
+ checked: checkMode === "steer",
941
+ disabled: !ready || !checkOn,
942
+ onChange: (next) => {
943
+ setCheckMode(next ? "steer" : "observe");
944
+ setAdvancedDirty(true);
945
+ }
946
+ }
947
+ ),
948
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("selfCheckHint") }),
949
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
950
+ Toggle,
951
+ {
952
+ label: t("narrowObserveLabel"),
953
+ checked: narrowMode === "observe",
954
+ disabled: !ready || !narrowOn,
955
+ onChange: (next) => {
956
+ setNarrowMode(next ? "observe" : "enforce");
957
+ setAdvancedDirty(true);
958
+ }
959
+ }
960
+ ),
961
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "decision-muted", children: t("narrowHint") })
962
+ ] }),
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: [
966
+ t("narrowKeepLabel"),
967
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
968
+ "textarea",
969
+ {
970
+ className: "decision-prefixes",
971
+ rows: 3,
972
+ value: keepPrefixes,
973
+ disabled: !ready || !narrowOn,
974
+ placeholder: "mcp__openviking",
975
+ onChange: (event) => {
976
+ setKeepPrefixes(event.target.value);
977
+ setAdvancedDirty(true);
978
+ }
979
+ }
980
+ ),
981
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: t("narrowKeepHint") })
982
+ ] }),
983
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field decision-field-inline", children: [
984
+ t("narrowMaxLabel"),
985
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
986
+ "input",
987
+ {
988
+ className: "decision-narrow-cap",
989
+ type: "number",
990
+ min: 1,
991
+ max: 200,
992
+ step: 1,
993
+ value: maxCandidates,
994
+ disabled: !ready || !narrowOn,
995
+ placeholder: "20",
996
+ onChange: (event) => {
997
+ setMaxCandidates(event.target.value);
998
+ setAdvancedDirty(true);
999
+ }
1000
+ }
1001
+ ),
1002
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: t("narrowMaxHint") })
1003
+ ] }),
1004
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field decision-field-inline", children: [
1005
+ t("narrowThresholdLabel"),
1006
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1007
+ "input",
1008
+ {
1009
+ className: "decision-narrow-cap",
1010
+ type: "number",
1011
+ min: 0,
1012
+ max: 1,
1013
+ step: 0.05,
1014
+ value: threshold,
1015
+ disabled: !ready || !narrowOn,
1016
+ placeholder: "0.3",
1017
+ onChange: (event) => {
1018
+ setThreshold(event.target.value);
1019
+ setAdvancedDirty(true);
1020
+ }
1021
+ }
1022
+ ),
1023
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: t("narrowThresholdHint") })
1024
+ ] })
1025
+ ] })
1026
+ ] }),
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") }) })
1028
+ ] })
1029
+ ] }),
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" }),
1033
+ t("backend")
1034
+ ] }),
1035
+ ready && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "decision-muted decision-destination", children: [
1036
+ t("destination"),
1037
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: config.effectiveUrl })
1038
+ ] }),
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: [
1041
+ t("url"),
1042
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1043
+ "input",
1044
+ {
1045
+ type: "url",
1046
+ value: config.url,
1047
+ disabled: !ready,
1048
+ placeholder: "https://api.typesafe.ai",
1049
+ onChange: (event) => {
1050
+ setConfig({ ...config, url: event.target.value });
1051
+ setBackendDirty(true);
1052
+ setProbeResult(null);
1053
+ }
1054
+ }
1055
+ )
1056
+ ] }),
1057
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field", children: [
1058
+ t("model"),
1059
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1060
+ "input",
1061
+ {
1062
+ value: config.model,
1063
+ disabled: !ready,
1064
+ placeholder: "jev-latest",
1065
+ onChange: (event) => {
1066
+ setConfig({ ...config, model: event.target.value });
1067
+ setBackendDirty(true);
1068
+ setProbeResult(null);
1069
+ }
1070
+ }
1071
+ )
1072
+ ] }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "decision-field", children: [
1074
+ t("key"),
1075
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1076
+ "input",
1077
+ {
1078
+ type: "password",
1079
+ autoComplete: "off",
1080
+ value: key,
1081
+ disabled: !ready,
1082
+ placeholder: config.apiKeySet ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "",
1083
+ onChange: (event) => {
1084
+ setKey(event.target.value);
1085
+ setBackendDirty(true);
1086
+ setProbeResult(null);
1087
+ }
1088
+ }
1089
+ ),
1090
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { className: "decision-muted", children: config.apiKeySet ? t("keyHintSaved") : t("keyHintEmpty") })
1091
+ ] }),
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 })
1096
+ ] })
1097
+ ] }),
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") })
1102
+ ] }),
1103
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "decision-danger-button", disabled: busy || !ready, onClick: () => void removeSavedKey(), children: t("removeKeyButton") })
1104
+ ] })
1105
+ ] }),
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 })
1107
+ ] });
1108
+ }
1109
+
1110
+ // src/client/styles.ts
1111
+ var css = `
1112
+ .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
+ .decision-trigger:hover,.decision-trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover,#eef1f3);color:var(--dsw-alias-label-secondary,#59636e)}
1114
+ .decision-trigger-icon{flex:none;color:inherit}
1115
+ .decision-trigger-text{min-width:0;flex:0 1 auto;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}
1116
+ .decision-trigger-text b{color:inherit;font-weight:500;font-variant-numeric:tabular-nums}
1117
+ .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}
1118
+ .decision-dialog{box-sizing:border-box;flex-direction:column;width:min(46rem,calc(100vw - 2rem));max-height:min(74vh,40rem);border:1px solid var(--dsw-alias-border-l2,#dce1e6);border-radius:14px;padding:0;overflow:hidden;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#202124);box-shadow:0 1rem 3rem rgba(0,0,0,.18);font:inherit}
1119
+ .decision-dialog[open]{display:flex}
1120
+ .decision-dialog::backdrop{background:rgba(0,0,0,.45)}
1121
+ .decision-head{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.7rem 1.1rem;border-bottom:1px solid var(--dsw-alias-border-l2,#eceff1)}
1122
+ .decision-head h2{font-size:1.05rem;margin:0}
1123
+ .decision-head button{border:0;background:transparent;color:inherit;font:inherit;font-size:1.5rem;cursor:pointer;border-radius:8px;width:2rem;height:2rem}
1124
+ .decision-body{flex:1 1 auto;overflow:auto;padding:0 1.1rem .8rem}
1125
+ .decision-dialog section{border-top:1px solid var(--dsw-alias-border-l2,#eceff1);margin-top:.8rem;padding-top:.8rem}
1126
+ .decision-body>section:first-child{border-top:0;margin-top:0;padding-top:.8rem}
1127
+ .decision-dialog h3{font-size:.9rem;margin:0 0 .55rem}
1128
+ .decision-dialog p{font-size:.84rem;line-height:1.5;margin:.35rem 0}
1129
+ .decision-muted{color:var(--dsw-alias-label-secondary,#59636e);font-size:.76rem;line-height:1.5}
1130
+ .decision-empty{color:var(--dsw-alias-label-secondary,#59636e);font-size:.82rem;padding:.6rem .8rem;border:1px dashed var(--dsw-alias-border-l2,#dce1e6);border-radius:10px}
1131
+ .decision-cards{display:grid;grid-template-columns:1fr 1fr;gap:.75rem}
1132
+ .decision-card{border:1px solid var(--dsw-alias-border-l2,#e6eaee);border-radius:12px;padding:.85rem .95rem;background:linear-gradient(180deg,var(--dsw-alias-bg-layer-2,#fff),var(--dsw-alias-bg-layer-1,#fafbfc))}
1133
+ .decision-card-wide{grid-column:1 / -1}
1134
+ .decision-card-head{display:flex;align-items:baseline;justify-content:space-between;gap:.5rem;margin-bottom:.6rem}
1135
+ .decision-card-title{font-size:.8rem;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);letter-spacing:.02em}
1136
+ .decision-card-total{font-size:1.5rem;font-weight:700;line-height:1;color:var(--dsw-alias-label-primary,#202124);font-variant-numeric:tabular-nums}
1137
+ .decision-card-total small{font-size:.7rem;font-weight:500;color:var(--dsw-alias-label-tertiary,#77818b);margin-left:.15rem}
1138
+ .decision-bar{display:flex;height:8px;border-radius:999px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,#eef1f3)}
1139
+ .decision-bar-empty{background:var(--dsw-alias-bg-layer-1,#eef1f3)}
1140
+ .decision-bar-allow{background:#1f8a70}
1141
+ .decision-bar-ask{background:#c98a1e}
1142
+ .decision-bar-deny{background:#c0392b}
1143
+ .decision-legend{list-style:none;display:flex;flex-wrap:wrap;gap:.4rem 1rem;margin:.6rem 0 0;padding:0}
1144
+ .decision-legend li{display:flex;align-items:center;gap:.35rem;font-size:.78rem;color:var(--dsw-alias-label-secondary,#59636e)}
1145
+ .decision-legend b{color:var(--dsw-alias-label-primary,#202124);font-variant-numeric:tabular-nums}
1146
+ .decision-legend-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
1147
+ .decision-dot-allow{background:#1f8a70}
1148
+ .decision-dot-ask{background:#c98a1e}
1149
+ .decision-dot-deny{background:#c0392b}
1150
+ .decision-card-foot{display:flex;justify-content:space-between;gap:1rem;margin-top:.7rem;padding-top:.6rem;border-top:1px solid var(--dsw-alias-border-l2,#eef1f3);font-size:.76rem;color:var(--dsw-alias-label-secondary,#59636e)}
1151
+ .decision-card-foot b{color:var(--dsw-alias-label-primary,#202124);font-variant-numeric:tabular-nums;margin-left:.3rem}
1152
+ .decision-card-rate{justify-content:center}
1153
+ .decision-card-stats{display:flex;gap:.6rem}
1154
+ .decision-stat{flex:1 1 0;display:flex;flex-direction:column;align-items:center;gap:.2rem;padding:.5rem .3rem;border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f6f8fa)}
1155
+ .decision-stat-num{font-size:1.35rem;font-weight:700;line-height:1;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#202124)}
1156
+ .decision-stat-warn{color:#c98a1e}
1157
+ .decision-stat-muted{color:var(--dsw-alias-label-tertiary,#9aa5b1)}
1158
+ .decision-stat-label{font-size:.72rem;color:var(--dsw-alias-label-secondary,#59636e)}
1159
+ .decision-log{list-style:none;margin:0;padding:0;display:grid;gap:.5rem;max-height:13rem;overflow:auto}
1160
+ .decision-log-item{display:flex;align-items:baseline;gap:.6rem;font-size:.8rem;padding-left:.6rem;border-left:2px solid var(--dsw-alias-border-l2,#dce1e6);line-height:1.5}
1161
+ .decision-log-item.decision-log-deny{border-left-color:#c0392b}
1162
+ .decision-log-item.decision-log-error,.decision-log-item.decision-log-warn{border-left-color:#c98a1e}
1163
+ .decision-log-item.decision-log-ok{border-left-color:#1f8a70}
1164
+ .decision-log-time{color:var(--dsw-alias-label-tertiary,#77818b);font-variant-numeric:tabular-nums;flex:0 0 auto}
1165
+ .decision-log-body{color:var(--dsw-alias-label-primary,#202124);flex:1 1 auto;min-width:0}
1166
+ .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
+ .decision-log-verdict{font-weight:600}
1168
+ .decision-log-deny .decision-log-verdict{color:#c0392b}
1169
+ .decision-log-ok .decision-log-verdict{color:#1f8a70}
1170
+ .decision-log-warn .decision-log-verdict{color:#c98a1e}
1171
+ .decision-log-detail{color:var(--dsw-alias-label-secondary,#59636e)}
1172
+
1173
+ .decision-status{display:flex;align-items:center;gap:.5rem;margin-top:.7rem;font-size:.82rem}
1174
+ .decision-status-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;background:#9aa5b1}
1175
+ .decision-status-ok{background:#1f8a70}
1176
+ .decision-status-down{background:#c0392b}
1177
+ .decision-status-checking{background:#c98a1e}
1178
+ .decision-status-idle{background:#9aa5b1}
1179
+ .decision-status-label{color:var(--dsw-alias-label-secondary,#59636e)}
1180
+ .decision-status-recheck{margin-left:auto;border:1px solid var(--dsw-alias-border-l2,#ccd3da);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#59636e);padding:.15rem .55rem;font:inherit;font-size:.76rem;cursor:pointer}
1181
+ .decision-status-recheck:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1,#f2f4f6);color:var(--dsw-alias-label-primary,#202124)}
1182
+ .decision-status-recheck:disabled{opacity:.5;cursor:default}
1183
+
1184
+ .decision-config{display:grid;gap:0;color:var(--dsw-alias-label-primary,#202124);container-type:inline-size}
1185
+ .decision-config section{margin-top:1.25rem;padding-top:1.25rem;border-top:1px solid var(--dsw-alias-border-l2,#eceff1)}
1186
+ .decision-config section:first-child{margin-top:0;padding-top:0;border-top:0}
1187
+ .decision-config h3{font-size:1rem;font-weight:600;margin:0 0 .7rem;display:flex;align-items:center;gap:.55rem}
1188
+ .decision-head-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1.7rem;height:1.7rem;border-radius:8px}
1189
+ .decision-head-icon-features{color:#1f8a70;background:rgba(31,138,112,.14)}
1190
+ .decision-head-icon-advanced{color:#c98a1e;background:rgba(201,138,30,.16)}
1191
+ .decision-head-icon-backend{color:#5b6ee1;background:rgba(91,110,225,.15)}
1192
+ .decision-collapse{margin-top:1.25rem;padding-top:1.25rem;border-top:1px solid var(--dsw-alias-border-l2,#eceff1)}
1193
+ .decision-collapse-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;list-style:none;cursor:pointer;border-radius:8px}
1194
+ .decision-collapse-head::-webkit-details-marker{display:none}
1195
+ .decision-collapse-head h3{margin:0}
1196
+ .decision-collapse-head:hover h3{color:var(--dsw-alias-label-primary,#202124)}
1197
+ .decision-collapse-head:focus-visible{outline:2px solid #1f5d50;outline-offset:2px}
1198
+ .decision-collapse-icon{flex:0 0 auto;position:relative;width:1.6rem;height:1.6rem;border:1px solid var(--dsw-alias-border-l2,#ccd3da);border-radius:8px}
1199
+ .decision-collapse-icon::before,.decision-collapse-icon::after{content:"";position:absolute;top:50%;left:50%;background:var(--dsw-alias-label-secondary,#59636e);transform:translate(-50%,-50%)}
1200
+ .decision-collapse-icon::before{width:.7rem;height:1.5px}
1201
+ .decision-collapse-icon::after{width:1.5px;height:.7rem;transition:transform .15s ease,opacity .15s ease}
1202
+ .decision-collapse[open] .decision-collapse-icon::after{transform:translate(-50%,-50%) scaleY(0);opacity:0}
1203
+ .decision-collapse-body{margin-top:.7rem}
1204
+ .decision-config p{font-size:.84rem;line-height:1.6;margin:.45rem 0}
1205
+ .decision-destination{display:flex;flex-direction:column;gap:.25rem;align-items:flex-start}
1206
+ .decision-destination code{font-size:.78rem;padding:.15rem .4rem;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#f2f4f6);color:var(--dsw-alias-label-primary,#202124);word-break:break-all}
1207
+ .decision-config form{display:grid;gap:1rem;margin-top:.8rem}
1208
+ .decision-field{display:grid;gap:.4rem;font-size:.82rem;font-weight:600}
1209
+ .decision-field .decision-muted{font-weight:400;margin:.1rem 0 0}
1210
+ .decision-config input:not([type=checkbox]){box-sizing:border-box;width:100%;min-height:2.4rem;border:1px solid var(--dsw-alias-border-l2,#ccd3da);border-radius:8px;padding:.4rem .7rem;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit;font:inherit;font-weight:400}
1211
+ .decision-config input:not([type=checkbox]):disabled{background:var(--dsw-alias-bg-layer-1,#f6f8fa);opacity:.7}
1212
+ .decision-config textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2,#ccd3da);border-radius:8px;padding:.4rem .7rem;background:var(--dsw-alias-bg-layer-2,#fff);color:inherit;font:inherit;font-weight:400;resize:vertical;min-height:3.4rem}
1213
+ .decision-config textarea:disabled{background:var(--dsw-alias-bg-layer-1,#f6f8fa);opacity:.7}
1214
+ .decision-field-textarea{margin-top:.7rem}
1215
+ .decision-field-inline{margin-top:.7rem}
1216
+ .decision-narrow-cap{width:6rem!important}
1217
+ .decision-prefixes{font-family:var(--dsw-alias-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace)!important;line-height:1.5}
1218
+
1219
+ .decision-field-block{display:grid;gap:.35rem}
1220
+ .decision-field-block+.decision-field-block{margin-top:1.1rem}
1221
+ .decision-field-block .decision-muted{font-weight:400;margin:0}
1222
+ .decision-advanced-grid{display:grid;grid-template-columns:1fr;gap:1.1rem;align-items:start;justify-content:start}
1223
+ .decision-advanced-grid .decision-field-block+.decision-field-block{margin-top:0}
1224
+ @container (min-width:34rem){.decision-advanced-grid{grid-template-columns:repeat(2,minmax(0,28rem));column-gap:2rem}}
1225
+ .decision-subhead{font-size:.86rem;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e)}
1226
+ .decision-toggle{display:flex;align-items:center;justify-content:space-between;gap:1rem;font-size:.86rem;font-weight:600;cursor:pointer}
1227
+ .decision-toggle-text{flex:1 1 auto}
1228
+ .decision-toggle-box{position:relative;display:inline-flex;flex:0 0 auto;width:38px;height:22px}
1229
+ .decision-toggle-input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}
1230
+ .decision-toggle-input:disabled{cursor:default}
1231
+ .decision-toggle-track{box-sizing:border-box;position:absolute;inset:0;width:38px;height:22px;border:1px solid var(--dsw-alias-border-l2,#ccd3da);border-radius:999px;background:var(--dsw-alias-bg-layer-1,#f2f4f6);transition:background .15s ease,border-color .15s ease;pointer-events:none}
1232
+ .decision-toggle-track::after{content:"";position:absolute;top:50%;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.3);transform:translateY(-50%);transition:transform .15s ease}
1233
+ .decision-toggle-input:checked+.decision-toggle-track{background:#1f5d50;border-color:#1f5d50}
1234
+ .decision-toggle-input:checked+.decision-toggle-track::after{transform:translate(16px,-50%)}
1235
+ .decision-toggle-input:disabled+.decision-toggle-track{opacity:.5}
1236
+ .decision-toggle-input:focus-visible+.decision-toggle-track{outline:2px solid #1f5d50;outline-offset:2px}
1237
+
1238
+ .decision-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;margin-top:.2rem}
1239
+ .decision-actions button{border:1px solid #1f4038;border-radius:8px;background:#1f4038;color:#fff;padding:.5rem .9rem;font:inherit;font-size:.83rem;font-weight:500;cursor:pointer}
1240
+ .decision-actions .decision-secondary{background:transparent;color:#1f4038}
1241
+ .decision-actions .decision-secondary:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1,#f2f4f6)}
1242
+ .decision-actions button:disabled{opacity:.5;cursor:not-allowed}
1243
+ .decision-probe-result{font-size:.82rem;font-weight:500}
1244
+ .decision-danger-row{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-top:1rem;padding-top:1rem;border-top:1px dashed var(--dsw-alias-border-l2,#dce1e6)}
1245
+ .decision-danger-text{display:grid;gap:.25rem;min-width:0}
1246
+ .decision-danger-title{font-size:.84rem;font-weight:600;color:var(--dsw-alias-label-primary,#202124)}
1247
+ .decision-danger-text .decision-muted{font-weight:400}
1248
+ .decision-danger-button{flex:0 0 auto;border:1px solid #c0392b;border-radius:8px;background:transparent;color:#c0392b;padding:.4rem .9rem;font:inherit;font-size:.82rem;font-weight:500;cursor:pointer}
1249
+ .decision-danger-button:hover:not(:disabled){background:#fdecea}
1250
+ .decision-danger-button:disabled{opacity:.5;cursor:not-allowed}
1251
+ .decision-danger-button:focus-visible{outline:2px solid #c0392b;outline-offset:2px}
1252
+ .decision-message{font-size:.82rem;margin:.2rem 0 0}
1253
+ .decision-message-ok{color:#1f6f57}
1254
+ .decision-message-warn{color:#9d4b1f}
1255
+ `;
1256
+ function installStyles() {
1257
+ if (typeof document === "undefined" || document.querySelector('style[data-plugin="dsh-decision-layer"]')) return () => {
1258
+ };
1259
+ const element = document.createElement("style");
1260
+ element.dataset.plugin = "dsh-decision-layer";
1261
+ element.textContent = css;
1262
+ document.head.appendChild(element);
1263
+ return () => element.remove();
1264
+ }
1265
+
1266
+ // src/client/index.tsx
1267
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1268
+ var inject = ["slots", "locale"];
1269
+ function apply(ctx) {
1270
+ ctx.effect(installStyles);
1271
+ ctx.effect(() => ctx.locale.register("dsh-decision-layer", dictionaries));
1272
+ ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
1273
+ name: "conversation.composer.dock",
1274
+ id: "decision-layer-panel",
1275
+ order: 20,
1276
+ locale: "dsh-decision-layer",
1277
+ registrant: "dsh-decision-layer",
1278
+ inject: (sessionId) => ({ sessionId })
1279
+ }, DecisionPanel));
1280
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register(
1281
+ { 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 })
1283
+ ));
1284
+ }
1285
+
1286
+ return module.exports;
1287
+ }
1288
+ });