dsh-harbor-evolution 0.4.0 → 0.5.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 CHANGED
@@ -48,220 +48,154 @@ var harbor_ocean_default = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4
48
48
 
49
49
  // src/client/index.jsx
50
50
  var NS = "harbor-evolution";
51
- var ROUTE = "/_dsh/harbor-evolution/dashboard";
51
+ var API = "/_dsh/harbor-evolution";
52
52
  var dictionaries = {
53
53
  zh: {
54
54
  tab: "Harbor",
55
55
  settings: "Harbor \u81EA\u8FDB\u5316",
56
- eyebrow: "HARBOR SELF-EVOLVING",
57
- heroTitle: "\u8BA9\u6BCF\u6B21 Agent \u6539\u52A8\u90FD\u6709\u8BC1\u636E",
58
- heroBody: "Candidate \u56FA\u5316\u80FD\u529B\uFF0CJob \u4FDD\u5B58\u5B9E\u9A8C\uFF0CPromotion Gate \u53EA\u8BA9\u53EF\u6BD4\u8F83\u3001\u65E0\u56DE\u5F52\u7684\u7248\u672C\u9760\u5CB8\u3002",
56
+ eyebrow: "EVALUATION WORKBENCH",
57
+ heroTitle: "\u7A33\u5B9A\u5730\u770B\u89C1 Agent \u662F\u5426\u771F\u7684\u8FDB\u6B65",
58
+ heroBody: "Evaluation Stack \u56FA\u5B9A\u8BC4\u6D4B\u542B\u4E49\uFF0CContext v2 \u56FA\u5B9A\u53EF\u6BD4\u8F83\u6027\uFF0CPromotion Gate \u53EA\u63A5\u53D7\u6709\u8BC1\u636E\u4E14\u65E0\u56DE\u5F52\u7684 Candidate\u3002",
59
59
  refresh: "\u5237\u65B0",
60
- refreshed: "\u6700\u8FD1\u5237\u65B0",
61
- totalJobs: "\u8BC4\u6D4B\u6279\u6B21",
62
- completedJobs: "\u5DF2\u5B8C\u6210",
63
- activeJobs: "\u8FDB\u884C\u4E2D",
64
- failedJobs: "\u5931\u8D25",
65
- latestMetric: "\u6700\u65B0\u6307\u6807",
66
- jobs: "\u6700\u8FD1\u7684 Harbor Jobs",
67
- jobsHint: "\u6700\u591A\u5C55\u793A\u6700\u8FD1 50 \u4E2A\u6279\u6B21\u3002\u7ED3\u679C\u76F4\u63A5\u8BFB\u53D6\u7A33\u5B9A\u7684 evaluation-summary.json\u3002",
68
- emptyTitle: "\u8FD8\u6CA1\u6709\u8BC4\u6D4B\u7ED3\u679C",
69
- emptyBody: "\u8BA9 Agent \u8C03\u7528\u5B98\u65B9 Skill\uFF0C\u5148\u6F84\u6E05 Candidate\u3001Dataset \u4E0E Promotion Policy\uFF0C\u518D\u542F\u52A8\u7B2C\u4E00\u4E2A Job\u3002",
70
- startStep1: "1. \u56FA\u5316 Candidate",
71
- startStep2: "2. \u8FD0\u884C Harbor Job",
72
- startStep3: "3. \u6BD4\u8F83\u5E76\u51B3\u5B9A\u664B\u7EA7",
60
+ jobs: "\u8BC4\u6D4B\u6279\u6B21",
61
+ jobsHint: "\u8F7B\u91CF\u603B\u89C8\uFF1B\u70B9\u51FB Job \u6253\u5F00\u5B8C\u6574\u8BC4\u6D4B\u5DE5\u4F5C\u53F0\u3002",
62
+ empty: "\u8FD8\u6CA1\u6709 Context v2 Job\u3002\u5148\u8C03\u7528\u5B98\u65B9 Skill \u5B8C\u6210\u67B6\u6784\u6F84\u6E05\u548C\u521D\u59CB\u5316\u3002",
63
+ completed: "\u5B8C\u6210",
64
+ partial: "\u5E26\u5F02\u5E38",
65
+ failed: "\u5931\u8D25",
66
+ pending: "\u8FD0\u884C\u4E2D",
73
67
  candidate: "Candidate",
74
- context: "\u8BC4\u6D4B\u4E0A\u4E0B\u6587",
68
+ context: "Context v2",
75
69
  trials: "Trials",
76
- exceptions: "Exceptions",
77
- metrics: "\u6307\u6807",
78
- pending: "\u4E0B\u6F5C\u4E2D",
79
- completed: "\u5DF2\u9760\u5CB8",
80
- partial: "\u5E26\u5F02\u5E38\u5B8C\u6210",
81
- failed: "\u9047\u5230\u98CE\u66B4",
82
- promote: "\u664B\u7EA7",
83
- reject: "\u62D2\u7EDD\u664B\u7EA7",
84
- noMetrics: "\u6682\u65E0\u6307\u6807",
85
- loading: "\u6B63\u5728\u8BFB\u53D6 Harbor \u822A\u7EBF\u2026",
86
- loadError: "\u65E0\u6CD5\u8BFB\u53D6 Harbor \u72B6\u6001",
87
- retry: "\u91CD\u8BD5",
88
- doctorTitle: "\u5B89\u88C5\u4E0E\u8FD0\u884C\u68C0\u67E5",
89
- doctorBody: "\u8FD9\u91CC\u68C0\u67E5 DSH Plugin \u5230 Harbor Runtime \u7684\u5173\u952E\u8DEF\u5F84\u3002\u914D\u7F6E\u4ECD\u7531 Cordis Profile \u7BA1\u7406\uFF0C\u4E0D\u4F1A\u88AB\u6D4F\u89C8\u5668\u9759\u9ED8\u6539\u5199\u3002",
90
- ready: "\u53EF\u4EE5\u5F00\u59CB\u8BC4\u6D4B",
91
- attention: "\u9700\u8981\u5904\u7406",
92
- projectRoot: "\u9879\u76EE\u6839\u76EE\u5F55",
93
- jobsDir: "Jobs \u76EE\u5F55",
94
- harbor: "Harbor CLI",
95
- harborDsh: "Promotion CLI",
96
- runtime: "\u8FD0\u884C\u5951\u7EA6",
97
- dshVersion: "DSH \u7248\u672C",
98
- agentImport: "Agent \u5165\u53E3",
99
- pluginImport: "Harbor Plugin",
100
- skillGuide: "\u5982\u4F55\u5F00\u59CB",
101
- skillBody: "\u5728\u5BF9\u8BDD\u4E2D\u8BF4\u660E\u201C\u8BF7\u7528 Harbor \u81EA\u8FDB\u5316 Skill \u521D\u59CB\u5316\u5E76\u8BC4\u6D4B\u6211\u7684 Agent\u201D\u3002Skill \u4F1A\u5148\u8865\u9F50\u9700\u6C42\uFF0C\u800C\u4E0D\u662F\u76F4\u63A5\u6267\u884C\u9AD8\u6210\u672C Job\u3002",
102
- toolSnapshot: "\u56FA\u5316 Candidate",
103
- toolRun: "\u8FD0\u884C Harbor \u8BC4\u6D4B",
104
- toolResult: "\u8BFB\u53D6\u8BC4\u6D4B\u7ED3\u679C",
105
- toolCompare: "\u6267\u884C Promotion Gate",
106
- running: "\u6267\u884C\u4E2D",
107
- toolFailed: "\u5DE5\u5177\u8C03\u7528\u5931\u8D25",
108
- noResult: "\u6CA1\u6709\u53EF\u89E3\u6790\u7684\u7ED3\u6784\u5316\u7ED3\u679C",
109
- digest: "Digest",
70
+ exceptions: "\u5F02\u5E38",
71
+ mode: "\u6A21\u5F0F",
72
+ result: "\u7ED3\u679C\u6458\u8981",
73
+ process: "\u8BC4\u6D4B\u8FC7\u7A0B",
74
+ contract: "\u8BC4\u6D4B\u5951\u7EA6",
75
+ stack: "Evaluation Stack",
76
+ dataset: "Dataset",
77
+ assessments: "Trial \u8BC4\u6D4B",
78
+ reasons: "\u539F\u56E0\u4E0E\u8BC1\u636E",
79
+ optimization: "\u4F18\u5316\u5EFA\u8BAE",
80
+ promotion: "\u664B\u7EA7\u51B3\u7B56",
81
+ audit: "\u5BA1\u8BA1\u4EA7\u7269",
82
+ close: "\u5173\u95ED",
83
+ search: "\u641C\u7D22 Trial",
84
+ all: "\u5168\u90E8",
85
+ assessed: "\u5DF2\u8BC4\u6D4B",
86
+ infra: "\u57FA\u7840\u8BBE\u65BD\u5F02\u5E38",
87
+ previous: "\u4E0A\u4E00\u9875",
88
+ next: "\u4E0B\u4E00\u9875",
89
+ noData: "\u6682\u65E0\u4EA7\u7269",
90
+ validation: "\u4EA7\u7269\u6821\u9A8C",
91
+ ready: "\u53EF\u7528\u4E8E\u6B63\u5F0F\u8BC4\u6D4B",
92
+ blocked: "\u5B58\u5728\u963B\u65AD\u9879",
93
+ doctor: "Architecture Doctor",
94
+ primaryMetric: "\u4E3B\u6307\u6807",
95
+ population: "\u6837\u672C\u5206\u5E03",
96
+ component: "\u7EC4\u4EF6",
110
97
  version: "\u7248\u672C",
111
- job: "Job",
112
- decision: "\u51B3\u7B56",
113
- reasons: "Gate \u539F\u56E0",
114
- comparable: "\u540C\u4E00\u8BC4\u6D4B\u4E0A\u4E0B\u6587",
115
- changed: "Candidate \u5DF2\u6539\u53D8"
98
+ digest: "Digest",
99
+ source: "\u8BC1\u636E",
100
+ findings: "Findings",
101
+ output: "\u8F93\u51FA\u4E0E\u4E2D\u95F4\u7ED3\u679C",
102
+ setupDoctor: "\u5B89\u88C5\u4E0E\u67B6\u6784\u68C0\u67E5",
103
+ setupHint: "\u8FD9\u91CC\u4EC5\u5C55\u793A\u72B6\u6001\uFF0C\u4E0D\u4F1A\u4ECE\u6D4F\u89C8\u5668\u6539\u5199 Cordis \u914D\u7F6E\u3002",
104
+ retry: "\u91CD\u8BD5",
105
+ loading: "\u6B63\u5728\u8BFB\u53D6\u2026"
116
106
  },
117
107
  en: {
118
108
  tab: "Harbor",
119
109
  settings: "Harbor Evolution",
120
- eyebrow: "HARBOR SELF-EVOLVING",
121
- heroTitle: "Make every Agent change evidence-based",
122
- heroBody: "Candidates freeze capabilities, Jobs preserve experiments, and the Promotion Gate admits only comparable, non-regressing versions.",
110
+ eyebrow: "EVALUATION WORKBENCH",
111
+ heroTitle: "See whether the Agent actually improved",
112
+ heroBody: "Evaluation Stack fixes meaning, Context v2 fixes comparability, and the Promotion Gate accepts only evidenced, non-regressing Candidates.",
123
113
  refresh: "Refresh",
124
- refreshed: "Last refreshed",
125
- totalJobs: "Evaluation jobs",
126
- completedJobs: "Completed",
127
- activeJobs: "Active",
128
- failedJobs: "Failed",
129
- latestMetric: "Latest metric",
130
- jobs: "Recent Harbor Jobs",
131
- jobsHint: "Shows up to 50 recent jobs from stable evaluation-summary.json files.",
132
- emptyTitle: "No evaluation results yet",
133
- emptyBody: "Ask the official Skill to clarify the Candidate, Dataset, and Promotion Policy before starting the first Job.",
134
- startStep1: "1. Freeze Candidate",
135
- startStep2: "2. Run Harbor Job",
136
- startStep3: "3. Compare and promote",
114
+ jobs: "Evaluation jobs",
115
+ jobsHint: "Lightweight overview; open a Job for the full workbench.",
116
+ empty: "No Context v2 Jobs yet. Use the official Skill to clarify and initialize the architecture.",
117
+ completed: "Completed",
118
+ partial: "Partial",
119
+ failed: "Failed",
120
+ pending: "Running",
137
121
  candidate: "Candidate",
138
- context: "Evaluation context",
122
+ context: "Context v2",
139
123
  trials: "Trials",
140
124
  exceptions: "Exceptions",
141
- metrics: "Metrics",
142
- pending: "Diving",
143
- completed: "Landed",
144
- partial: "Completed with exceptions",
145
- failed: "Storm",
146
- promote: "Promote",
147
- reject: "Reject",
148
- noMetrics: "No metrics",
149
- loading: "Reading Harbor routes\u2026",
150
- loadError: "Could not read Harbor status",
151
- retry: "Retry",
152
- doctorTitle: "Installation and runtime checks",
153
- doctorBody: "Checks the critical path from the DSH Plugin to the Harbor Runtime. Cordis Profile remains the configuration owner.",
154
- ready: "Ready to evaluate",
155
- attention: "Needs attention",
156
- projectRoot: "Project root",
157
- jobsDir: "Jobs directory",
158
- harbor: "Harbor CLI",
159
- harborDsh: "Promotion CLI",
160
- runtime: "Runtime contract",
161
- dshVersion: "DSH version",
162
- agentImport: "Agent entry",
163
- pluginImport: "Harbor plugin",
164
- skillGuide: "How to start",
165
- skillBody: "Say \u201Cuse the Harbor self-evolution Skill to initialize and evaluate my Agent\u201D. The Skill clarifies requirements before launching an expensive Job.",
166
- toolSnapshot: "Freeze Candidate",
167
- toolRun: "Run Harbor evaluation",
168
- toolResult: "Read evaluation result",
169
- toolCompare: "Run Promotion Gate",
170
- running: "Running",
171
- toolFailed: "Tool call failed",
172
- noResult: "No structured result could be parsed",
173
- digest: "Digest",
125
+ mode: "Mode",
126
+ result: "Outcome",
127
+ process: "Evaluation process",
128
+ contract: "Evaluation contract",
129
+ stack: "Evaluation Stack",
130
+ dataset: "Dataset",
131
+ assessments: "Trial assessments",
132
+ reasons: "Reasons and evidence",
133
+ optimization: "Optimization",
134
+ promotion: "Promotion",
135
+ audit: "Audit artifacts",
136
+ close: "Close",
137
+ search: "Search trials",
138
+ all: "All",
139
+ assessed: "Assessed",
140
+ infra: "Infrastructure error",
141
+ previous: "Previous",
142
+ next: "Next",
143
+ noData: "No artifact",
144
+ validation: "Artifact validation",
145
+ ready: "Promotion ready",
146
+ blocked: "Blocked",
147
+ doctor: "Architecture Doctor",
148
+ primaryMetric: "Primary metric",
149
+ population: "Population",
150
+ component: "Component",
174
151
  version: "Version",
175
- job: "Job",
176
- decision: "Decision",
177
- reasons: "Gate reasons",
178
- comparable: "Same evaluation context",
179
- changed: "Candidate changed"
152
+ digest: "Digest",
153
+ source: "Evidence",
154
+ findings: "Findings",
155
+ output: "Output and intermediate results",
156
+ setupDoctor: "Installation and architecture checks",
157
+ setupHint: "Read-only status; the browser never rewrites Cordis configuration.",
158
+ retry: "Retry",
159
+ loading: "Loading\u2026"
180
160
  }
181
161
  };
182
162
  var CSS = `
183
- .hse-root{--hse-blue:#2769ff;--hse-cyan:#3ed8ff;--hse-deep:#071d48;color:var(--dsw-alias-label-primary,#172033);height:100%;min-height:0;overflow:auto;background:var(--dsw-alias-bg-layer-1,#f5f8ff);font-family:inherit}
184
- .hse-page{width:min(1180px,calc(100% - 40px));margin:0 auto;padding:28px 0 56px}
185
- .hse-hero{position:relative;isolation:isolate;overflow:hidden;min-height:270px;border-radius:24px;padding:34px;color:#fff;background:#071d48 var(--hse-ocean) center/cover no-repeat;box-shadow:0 20px 60px rgba(4,27,74,.25)}
186
- .hse-hero:before{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(90deg,rgba(3,19,53,.96) 0%,rgba(4,28,75,.83) 45%,rgba(5,33,82,.24) 78%,rgba(2,19,48,.12) 100%)}
187
- .hse-hero:after{content:"";position:absolute;width:340px;height:340px;left:-120px;bottom:-250px;border-radius:50%;border:1px solid rgba(98,222,255,.3);box-shadow:0 0 0 38px rgba(80,196,255,.06),0 0 0 84px rgba(80,196,255,.035)}
188
- .hse-hero-copy{position:relative;z-index:1;max-width:610px}
189
- .hse-eyebrow{display:flex;align-items:center;gap:9px;font-size:11px;font-weight:800;letter-spacing:.16em;color:#79e1ff}
190
- .hse-eyebrow:before{content:"";width:22px;height:2px;border-radius:2px;background:#53d6ff;box-shadow:0 0 12px #53d6ff}
191
- .hse-hero h1{margin:18px 0 12px;font-size:clamp(27px,4vw,44px);line-height:1.08;letter-spacing:-.035em;max-width:540px}
192
- .hse-hero p{margin:0;max-width:570px;color:rgba(235,247,255,.82);font-size:15px;line-height:1.75}
193
- .hse-refresh{position:absolute;z-index:2;top:24px;right:24px;display:inline-flex;align-items:center;gap:7px;border:1px solid rgba(255,255,255,.3);border-radius:999px;padding:8px 13px;background:rgba(3,21,55,.4);color:#fff;font:inherit;font-size:12px;cursor:pointer;backdrop-filter:blur(10px);transition:.18s ease}
194
- .hse-refresh:hover{background:rgba(34,112,235,.55);transform:translateY(-1px)}
195
- .hse-refresh:disabled{opacity:.55;cursor:wait}
196
- .hse-stats{position:relative;z-index:2;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:30px;max-width:670px}
197
- .hse-stat{min-height:64px;padding:12px 14px;border:1px solid rgba(255,255,255,.16);border-radius:14px;background:rgba(4,27,65,.42);backdrop-filter:blur(8px)}
198
- .hse-stat span{display:block;font-size:11px;color:rgba(225,243,255,.68)}
199
- .hse-stat strong{display:block;margin-top:4px;font-size:22px;line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
200
- .hse-section{margin-top:28px}
201
- .hse-section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:13px}
202
- .hse-section h2,.hse-doctor h2{margin:0;font-size:18px;letter-spacing:-.01em}
203
- .hse-section-head p,.hse-doctor-intro{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#67728a);font-size:12px;line-height:1.55}
204
- .hse-job-list{display:grid;gap:12px}
205
- .hse-job{position:relative;padding:18px;border:1px solid var(--dsw-alias-border-l1,#dde5f2);border-radius:17px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:var(--dsw-shadow-lv1,0 5px 18px rgba(27,54,93,.06));overflow:hidden}
206
- .hse-job:before{content:"";position:absolute;inset:0 auto 0 0;width:3px;background:#5e8cff}
207
- .hse-job[data-status="completed"]:before{background:#23ba83}.hse-job[data-status="partial"]:before{background:#f1a23c}.hse-job[data-status="failed"]:before{background:#ed5b6c}
208
- .hse-job-top{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
209
- .hse-job-title{min-width:0}.hse-job-title strong{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.hse-job-title small{display:block;margin-top:5px;color:var(--dsw-alias-label-secondary,#748096);font-size:11px}
210
- .hse-status{flex:none;display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 9px;background:rgba(39,105,255,.1);color:#2769e8;font-size:11px;font-weight:700}
211
- .hse-status:before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px currentColor;opacity:.7}
212
- .hse-status[data-status="completed"]{color:#12835d;background:rgba(35,186,131,.11)}.hse-status[data-status="partial"]{color:#b36a08;background:rgba(241,162,60,.13)}.hse-status[data-status="failed"]{color:#ca3145;background:rgba(237,91,108,.12)}
213
- .hse-job-meta{display:grid;grid-template-columns:1.4fr 1.4fr .55fr .55fr;gap:8px;margin-top:15px}
214
- .hse-meta{min-width:0;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f5f8fc)}.hse-meta span{display:block;color:var(--dsw-alias-label-secondary,#748096);font-size:10px}.hse-meta code,.hse-meta strong{display:block;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:inherit}
215
- .hse-metrics{display:flex;flex-wrap:wrap;gap:7px;margin-top:11px}.hse-metric{display:inline-flex;gap:6px;align-items:center;border:1px solid var(--dsw-alias-border-l1,#dde5f2);border-radius:8px;padding:6px 8px;font-size:11px}.hse-metric b{color:#2769e8}
216
- .hse-promotion{margin-top:11px;padding:10px 12px;border-radius:10px;background:rgba(39,105,255,.07);font-size:11px}.hse-promotion[data-decision="PROMOTE"]{background:rgba(35,186,131,.1);color:#08734e}.hse-promotion[data-decision="REJECT"]{background:rgba(237,91,108,.09);color:#a92135}.hse-promotion strong{margin-right:8px}
217
- .hse-empty,.hse-error{padding:34px;border:1px dashed var(--dsw-alias-border-l1,#cfd9e8);border-radius:18px;text-align:center;background:var(--dsw-alias-bg-layer-2,#fff)}.hse-empty h3,.hse-error h3{margin:10px 0 7px}.hse-empty p,.hse-error p{max-width:600px;margin:0 auto;color:var(--dsw-alias-label-secondary,#748096);font-size:13px;line-height:1.6}
218
- .hse-steps{display:flex;justify-content:center;flex-wrap:wrap;gap:8px;margin-top:19px}.hse-steps span{padding:8px 11px;border-radius:9px;background:rgba(39,105,255,.08);color:#2769e8;font-size:11px;font-weight:700}
219
- .hse-spinner{width:28px;height:28px;margin:0 auto 12px;border:3px solid rgba(39,105,255,.14);border-top-color:#2769ff;border-radius:50%;animation:hse-spin .8s linear infinite}@keyframes hse-spin{to{transform:rotate(360deg)}}
220
- .hse-button{margin-top:13px;border:0;border-radius:9px;padding:8px 13px;background:#2769ff;color:#fff;font:inherit;font-size:12px;cursor:pointer}
221
- .hse-doctor{width:min(860px,calc(100% - 40px));margin:0 auto;padding:30px 0 54px}
222
- .hse-doctor-banner{display:flex;align-items:center;justify-content:space-between;gap:20px;margin:20px 0;padding:17px 19px;border-radius:15px;background:linear-gradient(135deg,rgba(39,105,255,.12),rgba(62,216,255,.08));border:1px solid rgba(39,105,255,.18)}
223
- .hse-doctor-banner strong{font-size:14px}.hse-doctor-banner span{font-size:12px;color:var(--dsw-alias-label-secondary,#748096)}
224
- .hse-checks{display:grid;grid-template-columns:1fr 1fr;gap:10px}.hse-check{display:flex;align-items:center;gap:11px;padding:13px;border:1px solid var(--dsw-alias-border-l1,#dde5f2);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff)}
225
- .hse-check-dot{width:9px;height:9px;border-radius:50%;background:#27bb83;box-shadow:0 0 0 4px rgba(39,187,131,.12)}.hse-check[data-status="warning"] .hse-check-dot{background:#e6a036;box-shadow:0 0 0 4px rgba(230,160,54,.12)}.hse-check[data-status="error"] .hse-check-dot{background:#ed5b6c;box-shadow:0 0 0 4px rgba(237,91,108,.12)}
226
- .hse-check div{min-width:0}.hse-check strong,.hse-check small{display:block}.hse-check strong{font-size:12px}.hse-check small{margin-top:3px;color:var(--dsw-alias-label-secondary,#748096);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
227
- .hse-contract{margin-top:20px;padding:18px;border:1px solid var(--dsw-alias-border-l1,#dde5f2);border-radius:15px;background:var(--dsw-alias-bg-layer-2,#fff)}.hse-contract h3{margin:0 0 12px;font-size:14px}.hse-contract dl{display:grid;grid-template-columns:130px minmax(0,1fr);gap:8px;margin:0;font-size:11px}.hse-contract dt{color:var(--dsw-alias-label-secondary,#748096)}.hse-contract dd{margin:0;overflow-wrap:anywhere}.hse-contract code{font-size:10px}
228
- .hse-skill-guide{margin-top:20px;padding:18px;border-radius:15px;color:#dff7ff;background:linear-gradient(135deg,#08275b,#0b4381);box-shadow:0 14px 35px rgba(5,39,91,.15)}.hse-skill-guide h3{margin:0 0 7px;font-size:14px}.hse-skill-guide p{margin:0;color:rgba(223,247,255,.78);font-size:12px;line-height:1.65}
229
- .hse-tool{border:1px solid var(--dsw-alias-border-l1,#dce4f1);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff);overflow:hidden}.hse-tool-head{display:flex;align-items:center;gap:10px;width:100%;padding:11px 13px;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.hse-tool-icon{display:grid;place-items:center;width:27px;height:27px;border-radius:9px;color:#2769ff;background:rgba(39,105,255,.1)}.hse-tool-title{font-size:12px;font-weight:700}.hse-tool-summary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary,#748096);font-size:11px}.hse-tool-state{margin-left:auto;font-size:10px;color:#238260}.hse-tool[data-state="running"] .hse-tool-state{color:#2769ff}.hse-tool[data-state="error"] .hse-tool-state{color:#cf3549}.hse-tool-body{display:grid;gap:8px;padding:0 13px 13px}.hse-tool-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}.hse-tool-field{min-width:0;padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f5f8fc)}.hse-tool-field span,.hse-tool-field strong,.hse-tool-field code{display:block}.hse-tool-field span{font-size:9px;color:var(--dsw-alias-label-secondary,#748096)}.hse-tool-field strong,.hse-tool-field code{margin-top:3px;font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hse-reasons{margin:0;padding-left:18px;color:var(--dsw-alias-label-secondary,#748096);font-size:10px;line-height:1.5}
230
- @media(max-width:760px){.hse-page,.hse-doctor{width:min(100% - 24px,1180px);padding-top:12px}.hse-hero{padding:26px 20px;min-height:310px}.hse-refresh{top:14px;right:14px}.hse-hero-copy{padding-top:28px}.hse-stats{grid-template-columns:repeat(2,1fr)}.hse-job-meta{grid-template-columns:1fr 1fr}.hse-checks{grid-template-columns:1fr}.hse-contract dl{grid-template-columns:1fr}.hse-contract dd{margin-bottom:5px}}
231
- @media(prefers-reduced-motion:reduce){.hse-spinner{animation:none}.hse-refresh{transition:none}}
163
+ .hse-root{--blue:#2769ff;--cyan:#44d9ff;--deep:#061b44;height:100%;min-height:0;overflow:auto;color:var(--dsw-alias-label-primary,#172033);background:var(--dsw-alias-bg-layer-1,#f4f7fc);font-family:inherit}.hse-page{width:min(1240px,calc(100% - 36px));margin:auto;padding:24px 0 56px}
164
+ .hse-hero{position:relative;isolation:isolate;overflow:hidden;min-height:230px;padding:30px;border-radius:24px;color:#fff;background:#061b44 var(--ocean) center/cover no-repeat;box-shadow:0 20px 60px rgba(4,27,74,.23)}.hse-hero:before{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(90deg,rgba(3,17,48,.96),rgba(4,31,79,.82) 52%,rgba(4,31,79,.2))}.hse-hero h1{max-width:650px;margin:16px 0 10px;font-size:clamp(28px,4vw,45px);line-height:1.08;letter-spacing:-.04em}.hse-hero p{max-width:670px;margin:0;color:#dceeff;font-size:14px;line-height:1.7}.hse-eyebrow{color:#76e4ff;font-size:11px;font-weight:800;letter-spacing:.17em}.hse-refresh{position:absolute;right:22px;top:22px;padding:8px 12px;border:1px solid #ffffff4a;border-radius:999px;color:#fff;background:#06245e99;cursor:pointer}
165
+ .hse-stats{display:flex;gap:9px;margin-top:25px;flex-wrap:wrap}.hse-stat{min-width:125px;padding:11px 13px;border:1px solid #ffffff26;border-radius:13px;background:#031a41a3;backdrop-filter:blur(8px)}.hse-stat span{display:block;color:#cde5fa;font-size:10px}.hse-stat b{display:block;margin-top:4px;font-size:20px}.hse-head{display:flex;justify-content:space-between;align-items:flex-end;gap:16px;margin:28px 0 12px}.hse-head h2{margin:0;font-size:18px}.hse-head p{margin:4px 0 0;color:#748096;font-size:12px}
166
+ .hse-list{display:grid;gap:10px}.hse-job{display:block;width:100%;padding:0;border:1px solid var(--dsw-alias-border-l1,#dce4f0);border-radius:16px;color:inherit;background:var(--dsw-alias-bg-layer-2,#fff);text-align:left;cursor:pointer;overflow:hidden;box-shadow:0 5px 18px #1b365d0d}.hse-job:hover{border-color:#78a3ff;transform:translateY(-1px)}.hse-job-body{padding:16px 18px}.hse-job-top{display:flex;justify-content:space-between;gap:14px}.hse-job-title{min-width:0}.hse-job-title strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px}.hse-job-title small{display:block;margin-top:4px;color:#7b879c;font-size:10px}.hse-status{flex:none;padding:5px 9px;border-radius:999px;color:#126d50;background:#23ba8318;font-size:10px;font-weight:700}.hse-status[data-status=failed]{color:#b5283d;background:#ed5b6c18}.hse-status[data-status=partial]{color:#9c620e;background:#f1a23c1c}.hse-status[data-status=pending]{color:#225cce;background:#2769ff18}.hse-meta-grid{display:grid;grid-template-columns:1.5fr 1.2fr .55fr .55fr .8fr;gap:7px;margin-top:13px}.hse-meta{min-width:0;padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f4f7fb)}.hse-meta span{display:block;color:#7b879c;font-size:9px}.hse-meta b,.hse-meta code{display:block;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:10px}.hse-metrics{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}.hse-pill{padding:5px 7px;border:1px solid var(--dsw-alias-border-l1,#dce4f0);border-radius:7px;font-size:10px}.hse-pill b{margin-left:5px;color:var(--blue)}
167
+ .hse-empty,.hse-error{padding:34px;border:1px dashed #cbd6e6;border-radius:16px;text-align:center;background:var(--dsw-alias-bg-layer-2,#fff);color:#748096;font-size:12px}.hse-spin{width:25px;height:25px;margin:0 auto 10px;border:3px solid #2769ff22;border-top-color:var(--blue);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
168
+ .hse-overlay{position:fixed;inset:0;z-index:1000;display:flex;justify-content:flex-end;background:#06142c7a;backdrop-filter:blur(3px)}.hse-drawer{width:min(1040px,94vw);height:100%;overflow:auto;background:var(--dsw-alias-bg-layer-1,#f4f7fc);box-shadow:-24px 0 70px #04142c52}.hse-drawer-head{position:sticky;top:0;z-index:3;display:flex;justify-content:space-between;gap:15px;padding:18px 22px;border-bottom:1px solid var(--dsw-alias-border-l1,#dce4f0);background:color-mix(in srgb,var(--dsw-alias-bg-layer-2,#fff) 92%,transparent);backdrop-filter:blur(12px)}.hse-drawer-head h2{margin:0;font-size:18px}.hse-drawer-head p{margin:5px 0 0;color:#748096;font-size:10px}.hse-close,.hse-button{border:0;border-radius:9px;padding:8px 11px;color:#fff;background:var(--blue);cursor:pointer}.hse-close{align-self:flex-start;background:#142a4f}.hse-workbench{padding:18px 22px 45px}.hse-section{margin-bottom:14px;padding:16px;border:1px solid var(--dsw-alias-border-l1,#dce4f0);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#fff)}.hse-section h3{margin:0 0 11px;font-size:14px}.hse-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.hse-kpi{padding:11px;border-radius:10px;background:linear-gradient(135deg,#2769ff12,#44d9ff0a)}.hse-kpi span{display:block;color:#748096;font-size:9px}.hse-kpi b{display:block;margin-top:4px;font-size:17px}.hse-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.hse-card{min-width:0;padding:11px;border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f4f7fb)}.hse-card span,.hse-card b,.hse-card code{display:block}.hse-card span{color:#748096;font-size:9px}.hse-card b,.hse-card code{margin-top:4px;overflow-wrap:anywhere;font-size:10px}.hse-findings{display:grid;gap:6px}.hse-finding{padding:9px 10px;border-left:3px solid #6d9cff;border-radius:7px;background:#2769ff0c;font-size:10px}.hse-finding[data-level=error]{border-color:#e75267;background:#e752670c}.hse-finding[data-level=warning]{border-color:#e6a036;background:#e6a0360c}.hse-finding b{margin-right:7px}.hse-components{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px}.hse-component{padding:9px;border-radius:9px;background:#0b2e6421}.hse-component span{display:block;color:#748096;font-size:9px}.hse-component b,.hse-component code{display:block;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:9px}
169
+ .hse-trial-tools{display:flex;gap:7px;margin-bottom:9px}.hse-input,.hse-select{min-width:0;padding:8px 9px;border:1px solid #cad6e7;border-radius:8px;color:inherit;background:transparent;font:inherit;font-size:10px}.hse-input{flex:1}.hse-table{width:100%;border-collapse:collapse;font-size:10px}.hse-table th,.hse-table td{padding:8px;border-bottom:1px solid #e4eaf2;text-align:left}.hse-table button{border:0;color:var(--blue);background:none;cursor:pointer;font:inherit}.hse-pager{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin-top:9px;font-size:10px}.hse-pager button{padding:5px 8px;border:1px solid #cad6e7;border-radius:7px;background:transparent;color:inherit;cursor:pointer}.hse-trial-detail{margin-top:10px;padding:12px;border-radius:10px;background:#071d48;color:#dcecff}.hse-trial-detail pre,.hse-audit pre{max-height:340px;overflow:auto;margin:8px 0 0;white-space:pre-wrap;word-break:break-word;font-size:9px;line-height:1.5}.hse-reasons{margin:0;padding-left:18px;font-size:10px;line-height:1.6}.hse-audit summary{cursor:pointer;font-size:11px;font-weight:700}.hse-valid{color:#14815d}.hse-invalid{color:#c33148}
170
+ .hse-settings{width:min(850px,calc(100% - 32px));margin:auto;padding:28px 0}.hse-checks{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-top:16px}.hse-check{padding:12px;border:1px solid #dce4f0;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff)}.hse-check b,.hse-check small{display:block}.hse-check small{margin-top:4px;color:#748096}.hse-tool{border:1px solid #dce4f0;border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff);overflow:hidden}.hse-tool button{display:flex;gap:8px;width:100%;padding:10px;border:0;color:inherit;background:transparent;text-align:left;cursor:pointer}.hse-tool strong{font-size:11px}.hse-tool small{margin-left:auto}.hse-tool pre{max-height:260px;overflow:auto;margin:0;padding:11px;border-top:1px solid #e3e9f1;white-space:pre-wrap;font-size:9px}
171
+ @media(max-width:800px){.hse-page{width:calc(100% - 20px)}.hse-hero{padding:24px 18px}.hse-meta-grid,.hse-kpis{grid-template-columns:repeat(2,1fr)}.hse-components{grid-template-columns:repeat(2,1fr)}.hse-grid,.hse-checks{grid-template-columns:1fr}.hse-drawer{width:100vw}.hse-workbench{padding:12px}.hse-drawer-head{padding:14px}.hse-table th:nth-child(3),.hse-table td:nth-child(3){display:none}}
172
+ @media(prefers-reduced-motion:reduce){.hse-spin{animation:none}.hse-job:hover{transform:none}}
232
173
  `;
233
174
  function installStyles() {
234
175
  const id = "dsh-harbor-evolution/client";
235
176
  if (document.querySelector(`style[data-plugin-css="${id}"]`)) return () => {
236
177
  };
237
178
  const style = document.createElement("style");
238
- style.dataset.plugin = "dsh-harbor-evolution";
239
179
  style.dataset.pluginCss = id;
240
180
  style.textContent = CSS;
241
181
  document.head.appendChild(style);
242
182
  return () => style.remove();
243
183
  }
244
184
  function isRecord(value) {
245
- return typeof value === "object" && value !== null && !Array.isArray(value);
185
+ return value && typeof value === "object" && !Array.isArray(value);
246
186
  }
247
- function blockText(block) {
248
- if (!isRecord(block) || !("kind" in block) || !Array.isArray(block.content)) return "";
249
- return block.content.filter((entry) => entry?.type === "text").map((entry) => entry.text).join("\n");
187
+ function format(value) {
188
+ return typeof value === "number" ? value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "") : String(value ?? "\u2014");
250
189
  }
251
- function decodeToolResult(block) {
252
- if (!isRecord(block) || !("kind" in block) || block.isError) return void 0;
253
- if (isRecord(block.meta)) return block.meta;
254
- const text = blockText(block).trim();
255
- if (!text) return void 0;
256
- try {
257
- const parsed = JSON.parse(text);
258
- return isRecord(parsed) ? parsed : void 0;
259
- } catch {
260
- return void 0;
261
- }
190
+ function short(value) {
191
+ return typeof value === "string" && value.length > 22 ? `${value.slice(0, 15)}\u2026${value.slice(-5)}` : value ?? "\u2014";
262
192
  }
263
- async function fetchDashboard() {
264
- const response = await fetch(ROUTE, { credentials: "same-origin", cache: "no-store" });
193
+ function reasonText(reason) {
194
+ return isRecord(reason) ? `${reason.code}: ${reason.message}` : String(reason);
195
+ }
196
+ async function api(route, params = {}) {
197
+ const query = new URLSearchParams(Object.entries(params).filter(([, value]) => value !== void 0 && value !== ""));
198
+ const response = await fetch(`${API}/${route}${query.size ? `?${query}` : ""}`, { credentials: "same-origin", cache: "no-store" });
265
199
  const body = await response.json();
266
200
  if (!response.ok || !body?.ok) throw new Error(body?.error?.message ?? `HTTP ${response.status}`);
267
201
  return body.value;
@@ -269,97 +203,104 @@ async function fetchDashboard() {
269
203
  function useDashboard(poll = true) {
270
204
  const [state, setState] = (0, import_react.useState)({ status: "loading" });
271
205
  const load = (0, import_react.useCallback)(async (quiet = false) => {
272
- if (!quiet) setState((current) => ({ ...current, status: current.value ? "refreshing" : "loading", error: void 0 }));
206
+ if (!quiet) setState((current) => ({ ...current, status: current.value ? "refreshing" : "loading" }));
273
207
  try {
274
- const value = await fetchDashboard();
275
- setState({ status: "ready", value });
208
+ setState({ status: "ready", value: await api("dashboard") });
276
209
  } catch (error) {
277
- setState((current) => ({ ...current, status: "error", error: error instanceof Error ? error.message : String(error) }));
210
+ setState((current) => ({ ...current, status: "error", error: error.message }));
278
211
  }
279
212
  }, []);
280
213
  (0, import_react.useEffect)(() => {
281
214
  void load();
282
- if (!poll) return void 0;
283
- const timer = window.setInterval(() => {
284
- void load(true);
285
- }, 5e3);
286
- return () => window.clearInterval(timer);
287
- }, [load, poll]);
215
+ }, [load]);
216
+ (0, import_react.useEffect)(() => {
217
+ if (!poll || !state.value) return void 0;
218
+ const interval = state.value.overview?.activeJobs ? 3e3 : 15e3;
219
+ const timer = window.setTimeout(() => void load(true), interval);
220
+ return () => window.clearTimeout(timer);
221
+ }, [load, poll, state.value]);
288
222
  return { ...state, load };
289
223
  }
290
- function WhaleIcon({ size = 18 }) {
291
- return /* @__PURE__ */ import_react.default.createElement("svg", { viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ import_react.default.createElement("path", { d: "M3 13c2.1 3.8 6.4 5.2 10.1 3.7 2.4-1 3.9-3 4.3-5.1 1.3.2 2.7-.3 3.6-1.4-1.5-.2-2.3-1-2.8-2.2-.7.8-1.2 1.8-1.2 2.8-2.9-.1-4.4-2.2-7.7-2.2C6.5 8.6 4.3 10 3 13Z" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "M7.2 12.3h.1" }));
224
+ function MetricPills({ metrics }) {
225
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-metrics" }, Object.entries(metrics ?? {}).map(([key, value]) => /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-pill", key }, key, /* @__PURE__ */ import_react.default.createElement("b", null, format(value)))));
292
226
  }
293
- function formatValue(value) {
294
- if (typeof value !== "number") return String(value ?? "\u2014");
295
- if (Number.isInteger(value)) return String(value);
296
- return value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
227
+ function JobCard({ job, t, open }) {
228
+ const candidate = job.candidate ?? {};
229
+ const context = job.evaluationContext ?? {};
230
+ return /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "hse-job", onClick: () => open(job.name) }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-job-body" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-job-top" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-job-title" }, /* @__PURE__ */ import_react.default.createElement("strong", null, job.name), /* @__PURE__ */ import_react.default.createElement("small", null, new Date(job.updatedAt).toLocaleString())), /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-status", "data-status": job.status }, t(job.status))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta-grid" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("candidate")), /* @__PURE__ */ import_react.default.createElement("b", null, candidate.candidate_id ?? "\u2014", " \xB7 ", candidate.version ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("context")), /* @__PURE__ */ import_react.default.createElement("code", { title: context.digest }, short(context.digest))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("trials")), /* @__PURE__ */ import_react.default.createElement("b", null, job.nTrials)), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("exceptions")), /* @__PURE__ */ import_react.default.createElement("b", null, job.nExceptions)), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("mode")), /* @__PURE__ */ import_react.default.createElement("b", null, job.mode ?? "\u2014"))), /* @__PURE__ */ import_react.default.createElement(MetricPills, { metrics: job.metrics })));
297
231
  }
298
- function shortDigest(value) {
299
- if (typeof value !== "string") return "\u2014";
300
- return value.length > 19 ? `${value.slice(0, 15)}\u2026${value.slice(-4)}` : value;
232
+ function ArtifactSection({ title, value, children }) {
233
+ return /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-section" }, /* @__PURE__ */ import_react.default.createElement("h3", null, title), children ?? (value ? /* @__PURE__ */ import_react.default.createElement("pre", null, JSON.stringify(value, null, 2)) : /* @__PURE__ */ import_react.default.createElement("span", null, "\u2014")));
301
234
  }
302
- function statusLabel(status, t) {
303
- return t(status in dictionaries.zh ? status : "pending");
304
- }
305
- function MetricPills({ metrics, t }) {
306
- const entries = Object.entries(isRecord(metrics) ? metrics : {});
307
- if (!entries.length) return /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-metric" }, t("noMetrics"));
308
- return entries.map(([name2, value]) => /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-metric", key: name2 }, name2, /* @__PURE__ */ import_react.default.createElement("b", null, formatValue(value))));
309
- }
310
- function JobCard({ job, t }) {
311
- const candidate = isRecord(job.candidate) ? job.candidate : {};
312
- const context = isRecord(job.evaluationContext) ? job.evaluationContext : {};
313
- return /* @__PURE__ */ import_react.default.createElement("article", { className: "hse-job", "data-status": job.status }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-job-top" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-job-title" }, /* @__PURE__ */ import_react.default.createElement("strong", null, job.name), /* @__PURE__ */ import_react.default.createElement("small", null, new Date(job.updatedAt).toLocaleString())), /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-status", "data-status": job.status }, statusLabel(job.status, t))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-job-meta" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("candidate")), /* @__PURE__ */ import_react.default.createElement("strong", null, candidate.candidate_id ?? "\u2014", " \xB7 ", candidate.version ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("context")), /* @__PURE__ */ import_react.default.createElement("code", { title: context.digest }, shortDigest(context.digest))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("trials")), /* @__PURE__ */ import_react.default.createElement("strong", null, job.nTrials)), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("exceptions")), /* @__PURE__ */ import_react.default.createElement("strong", null, job.nExceptions))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-metrics" }, /* @__PURE__ */ import_react.default.createElement(MetricPills, { metrics: job.metrics, t })), job.promotion ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-promotion", "data-decision": job.promotion.decision }, /* @__PURE__ */ import_react.default.createElement("strong", null, job.promotion.decision === "PROMOTE" ? t("promote") : t("reject")), job.promotion.reasons?.[0] ?? "") : null);
314
- }
315
- function EmptyState({ t }) {
316
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement(WhaleIcon, { size: 34 }), /* @__PURE__ */ import_react.default.createElement("h3", null, t("emptyTitle")), /* @__PURE__ */ import_react.default.createElement("p", null, t("emptyBody")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-steps" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("startStep1")), /* @__PURE__ */ import_react.default.createElement("span", null, t("startStep2")), /* @__PURE__ */ import_react.default.createElement("span", null, t("startStep3"))));
235
+ function TrialTable({ job, t }) {
236
+ const [query, setQuery] = (0, import_react.useState)("");
237
+ const [status, setStatus] = (0, import_react.useState)("");
238
+ const [offset, setOffset] = (0, import_react.useState)(0);
239
+ const [page, setPage] = (0, import_react.useState)();
240
+ const [detail, setDetail] = (0, import_react.useState)();
241
+ (0, import_react.useEffect)(() => {
242
+ const timer = window.setTimeout(() => {
243
+ void api("trials", { job, offset, limit: 50, query, status }).then(setPage);
244
+ }, 180);
245
+ return () => window.clearTimeout(timer);
246
+ }, [job, offset, query, status]);
247
+ const choose = async (trial) => setDetail(await api("trial", { job, trial }));
248
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-trial-tools" }, /* @__PURE__ */ import_react.default.createElement("input", { className: "hse-input", value: query, placeholder: t("search"), onChange: (event) => {
249
+ setQuery(event.target.value);
250
+ setOffset(0);
251
+ } }), /* @__PURE__ */ import_react.default.createElement("select", { className: "hse-select", value: status, onChange: (event) => {
252
+ setStatus(event.target.value);
253
+ setOffset(0);
254
+ } }, /* @__PURE__ */ import_react.default.createElement("option", { value: "" }, t("all")), /* @__PURE__ */ import_react.default.createElement("option", { value: "assessed" }, t("assessed")), /* @__PURE__ */ import_react.default.createElement("option", { value: "infrastructure-error" }, t("infra")))), /* @__PURE__ */ import_react.default.createElement("table", { className: "hse-table" }, /* @__PURE__ */ import_react.default.createElement("thead", null, /* @__PURE__ */ import_react.default.createElement("tr", null, /* @__PURE__ */ import_react.default.createElement("th", null, "Trial"), /* @__PURE__ */ import_react.default.createElement("th", null, "Status"), /* @__PURE__ */ import_react.default.createElement("th", null, "Metrics"))), /* @__PURE__ */ import_react.default.createElement("tbody", null, page?.items?.map((trial) => /* @__PURE__ */ import_react.default.createElement("tr", { key: trial.id }, /* @__PURE__ */ import_react.default.createElement("td", null, /* @__PURE__ */ import_react.default.createElement("button", { onClick: () => void choose(trial.id) }, trial.name ?? trial.id)), /* @__PURE__ */ import_react.default.createElement("td", null, trial.status), /* @__PURE__ */ import_react.default.createElement("td", null, Object.entries(trial.rewards ?? {}).map(([key, value]) => `${key} ${format(value)}`).join(" \xB7 ")))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-pager" }, /* @__PURE__ */ import_react.default.createElement("span", null, offset + 1, "\u2013", Math.min(offset + (page?.items?.length ?? 0), page?.total ?? 0), " / ", page?.total ?? 0), /* @__PURE__ */ import_react.default.createElement("button", { disabled: !offset, onClick: () => setOffset(Math.max(0, offset - 50)) }, t("previous")), /* @__PURE__ */ import_react.default.createElement("button", { disabled: !page?.hasMore, onClick: () => setOffset(offset + 50) }, t("next"))), detail ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-trial-detail" }, /* @__PURE__ */ import_react.default.createElement("b", null, t("output")), /* @__PURE__ */ import_react.default.createElement("pre", null, JSON.stringify(detail.assessment, null, 2))) : null);
317
255
  }
318
- function ErrorState({ error, retry, t }) {
319
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-error" }, /* @__PURE__ */ import_react.default.createElement("h3", null, t("loadError")), /* @__PURE__ */ import_react.default.createElement("p", null, error), /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-button", type: "button", onClick: () => void retry() }, t("retry")));
256
+ function Workbench({ job, close, t }) {
257
+ const [state, setState] = (0, import_react.useState)({ status: "loading" });
258
+ (0, import_react.useEffect)(() => {
259
+ let active = true;
260
+ void api("job", { job }).then((value) => active && setState({ status: "ready", value }), (error) => active && setState({ status: "error", error: error.message }));
261
+ return () => {
262
+ active = false;
263
+ };
264
+ }, [job]);
265
+ const detail = state.value;
266
+ const a = detail?.artifacts ?? {};
267
+ const summary = a.summary ?? {};
268
+ const context = a.context ?? {};
269
+ const doctor = a.doctor ?? {};
270
+ const contract = a.contract ?? {};
271
+ const stack = a.stack ?? {};
272
+ const reasons = a.promotion?.reasons ?? summary.exceptions ?? [];
273
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-overlay", role: "presentation", onMouseDown: (event) => event.target === event.currentTarget && close() }, /* @__PURE__ */ import_react.default.createElement("aside", { className: "hse-drawer", role: "dialog", "aria-modal": "true", "aria-label": job }, /* @__PURE__ */ import_react.default.createElement("header", { className: "hse-drawer-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("h2", null, job), /* @__PURE__ */ import_react.default.createElement("p", null, summary.candidate?.candidate_id ?? "\u2014", " \xB7 ", summary.candidate?.version ?? "\u2014", " \xB7 ", context.mode ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "hse-close", onClick: close }, t("close"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-workbench" }, state.status === "loading" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), t("loading")) : state.status === "error" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-error" }, state.error) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("result") }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-kpis" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-kpi" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("primaryMetric")), /* @__PURE__ */ import_react.default.createElement("b", null, format(summary.metrics?.[contract.primary_metric]))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-kpi" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("trials")), /* @__PURE__ */ import_react.default.createElement("b", null, summary.n_trials ?? 0)), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-kpi" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("exceptions")), /* @__PURE__ */ import_react.default.createElement("b", null, summary.n_exceptions ?? 0)), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-kpi" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("validation")), /* @__PURE__ */ import_react.default.createElement("b", { className: summary.artifact_validation?.valid ? "hse-valid" : "hse-invalid" }, summary.artifact_validation?.valid ? "VALID" : "INVALID"))), /* @__PURE__ */ import_react.default.createElement(MetricPills, { metrics: summary.metrics })), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("process") }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-grid" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-card" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("doctor")), /* @__PURE__ */ import_react.default.createElement("b", { className: doctor.promotion_ready ? "hse-valid" : "hse-invalid" }, doctor.promotion_ready ? t("ready") : t("blocked"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-card" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("context")), /* @__PURE__ */ import_react.default.createElement("code", null, short(context.digest)))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-findings" }, doctor.findings?.map((finding, index) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-finding", "data-level": finding.level, key: `${finding.code}-${index}` }, /* @__PURE__ */ import_react.default.createElement("b", null, finding.code), finding.message)))), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("contract") }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-grid" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-card" }, /* @__PURE__ */ import_react.default.createElement("span", null, "ID / ", t("version")), /* @__PURE__ */ import_react.default.createElement("b", null, contract.contract_id ?? "\u2014", " \xB7 ", contract.version ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-card" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("primaryMetric")), /* @__PURE__ */ import_react.default.createElement("b", null, contract.primary_metric ?? "\u2014"))), /* @__PURE__ */ import_react.default.createElement(MetricPills, { metrics: Object.fromEntries((contract.metrics ?? []).map((metric) => [metric.id, metric.direction ?? metric.label ?? "metric"])) })), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("stack") }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-components" }, Object.entries(stack.components ?? {}).map(([role, component]) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-component", key: role }, /* @__PURE__ */ import_react.default.createElement("span", null, role, component.reward_affecting ? " \xB7 reward" : ""), /* @__PURE__ */ import_react.default.createElement("b", null, component.id, " \xB7 ", component.version), /* @__PURE__ */ import_react.default.createElement("code", { title: component.digest }, short(component.digest)))))), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("dataset") }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-grid" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-card" }, /* @__PURE__ */ import_react.default.createElement("span", null, "ID / ", t("version")), /* @__PURE__ */ import_react.default.createElement("b", null, a.dataset?.dataset_id ?? "\u2014", " \xB7 ", a.dataset?.version ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-card" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("population")), /* @__PURE__ */ import_react.default.createElement("b", null, a.dataset?.task_count ?? 0, " tasks \xB7 ", a.dataset?.file_count ?? 0, " files")))), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("assessments") }, /* @__PURE__ */ import_react.default.createElement(TrialTable, { job, t })), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("reasons") }, /* @__PURE__ */ import_react.default.createElement("ul", { className: "hse-reasons" }, reasons.length ? reasons.map((reason, index) => /* @__PURE__ */ import_react.default.createElement("li", { key: index }, reasonText(reason))) : /* @__PURE__ */ import_react.default.createElement("li", null, t("noData")))), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("optimization"), value: a.optimization }), /* @__PURE__ */ import_react.default.createElement(ArtifactSection, { title: t("promotion"), value: a.promotion }), /* @__PURE__ */ import_react.default.createElement("details", { className: "hse-section hse-audit" }, /* @__PURE__ */ import_react.default.createElement("summary", null, t("audit")), /* @__PURE__ */ import_react.default.createElement("pre", null, JSON.stringify({ validation: detail.validation, candidate: a.candidate, context: a.context, stack: a.stack, dataset: a.dataset, population: a.population }, null, 2)))))));
320
274
  }
321
275
  function DashboardView({ t }) {
322
276
  const state = useDashboard(true);
277
+ const [selected, setSelected] = (0, import_react.useState)();
323
278
  const snapshot = state.value;
324
- const stats = [
325
- ["totalJobs", snapshot?.overview?.totalJobs ?? "\u2014"],
326
- ["completedJobs", snapshot?.overview?.completedJobs ?? "\u2014"],
327
- ["activeJobs", snapshot?.overview?.activeJobs ?? "\u2014"],
328
- ["latestMetric", snapshot?.overview?.latestMetric ? `${snapshot.overview.latestMetric.name} ${formatValue(snapshot.overview.latestMetric.value)}` : "\u2014"]
329
- ];
330
- return /* @__PURE__ */ import_react.default.createElement("main", { className: "hse-root" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-page" }, /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-hero", style: { "--hse-ocean": `url(${harbor_ocean_default})` } }, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "hse-refresh", disabled: state.status === "loading" || state.status === "refreshing", onClick: () => void state.load() }, /* @__PURE__ */ import_react.default.createElement("span", null, "\u21BB"), t("refresh")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-hero-copy" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-eyebrow" }, t("eyebrow")), /* @__PURE__ */ import_react.default.createElement("h1", null, t("heroTitle")), /* @__PURE__ */ import_react.default.createElement("p", null, t("heroBody"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stats" }, stats.map(([label, value]) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stat", key: label }, /* @__PURE__ */ import_react.default.createElement("span", null, t(label)), /* @__PURE__ */ import_react.default.createElement("strong", null, value))))), /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-section" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-section-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("h2", null, t("jobs")), /* @__PURE__ */ import_react.default.createElement("p", null, t("jobsHint"))), snapshot?.generatedAt ? /* @__PURE__ */ import_react.default.createElement("p", null, t("refreshed"), " ", new Date(snapshot.generatedAt).toLocaleTimeString()) : null), state.status === "loading" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spinner" }), /* @__PURE__ */ import_react.default.createElement("p", null, t("loading"))) : state.status === "error" && !snapshot ? /* @__PURE__ */ import_react.default.createElement(ErrorState, { error: state.error, retry: state.load, t }) : !snapshot?.jobs?.length ? /* @__PURE__ */ import_react.default.createElement(EmptyState, { t }) : /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-job-list" }, snapshot.jobs.map((job) => /* @__PURE__ */ import_react.default.createElement(JobCard, { job, t, key: job.name }))))));
279
+ const stats = [[t("jobs"), snapshot?.overview?.totalJobs ?? "\u2014"], [t("completed"), snapshot?.overview?.completedJobs ?? "\u2014"], [t("pending"), snapshot?.overview?.activeJobs ?? "\u2014"], [snapshot?.overview?.latestMetric?.name ?? t("primaryMetric"), format(snapshot?.overview?.latestMetric?.value)]];
280
+ return /* @__PURE__ */ import_react.default.createElement("main", { className: "hse-root" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-page" }, /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-hero", style: { "--ocean": `url(${harbor_ocean_default})` } }, /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-refresh", onClick: () => void state.load() }, t("refresh")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-eyebrow" }, t("eyebrow")), /* @__PURE__ */ import_react.default.createElement("h1", null, t("heroTitle")), /* @__PURE__ */ import_react.default.createElement("p", null, t("heroBody")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stats" }, stats.map(([label, value]) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-stat", key: label }, /* @__PURE__ */ import_react.default.createElement("span", null, label), /* @__PURE__ */ import_react.default.createElement("b", null, value))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("h2", null, t("jobs")), /* @__PURE__ */ import_react.default.createElement("p", null, t("jobsHint")))), state.status === "loading" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spin" }), t("loading")) : state.status === "error" && !snapshot ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-error" }, state.error, /* @__PURE__ */ import_react.default.createElement("br", null), /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-button", onClick: () => void state.load() }, t("retry"))) : !snapshot?.jobs?.length ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty" }, t("empty")) : /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-list" }, snapshot.jobs.map((job) => /* @__PURE__ */ import_react.default.createElement(JobCard, { job, t, open: setSelected, key: job.name })))), selected ? /* @__PURE__ */ import_react.default.createElement(Workbench, { job: selected, close: () => setSelected(void 0), t }) : null);
331
281
  }
332
282
  function DoctorView({ t }) {
333
283
  const state = useDashboard(false);
334
- const snapshot = state.value;
335
- const checks = snapshot?.checks ?? {};
336
- const allReady = Object.values(checks).every((check) => check?.status === "ok");
337
- const labels = { projectRoot: "projectRoot", jobsDir: "jobsDir", harbor: "harbor", harborDsh: "harborDsh" };
338
- return /* @__PURE__ */ import_react.default.createElement("main", { className: "hse-root" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-doctor" }, /* @__PURE__ */ import_react.default.createElement("h2", null, t("doctorTitle")), /* @__PURE__ */ import_react.default.createElement("p", { className: "hse-doctor-intro" }, t("doctorBody")), state.status === "loading" ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-empty", style: { marginTop: 20 } }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-spinner" }), /* @__PURE__ */ import_react.default.createElement("p", null, t("loading"))) : state.status === "error" && !snapshot ? /* @__PURE__ */ import_react.default.createElement("div", { style: { marginTop: 20 } }, /* @__PURE__ */ import_react.default.createElement(ErrorState, { error: state.error, retry: state.load, t })) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-doctor-banner" }, /* @__PURE__ */ import_react.default.createElement("strong", null, allReady ? t("ready") : t("attention")), /* @__PURE__ */ import_react.default.createElement("span", null, "Plugin v", snapshot?.pluginVersion ?? "0.4.0")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-checks" }, Object.entries(labels).map(([key, label]) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-check", "data-status": checks[key]?.status ?? "error", key }, /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-check-dot" }), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("strong", null, t(label)), /* @__PURE__ */ import_react.default.createElement("small", { title: checks[key]?.detail }, checks[key]?.detail ?? "\u2014"))))), /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-contract" }, /* @__PURE__ */ import_react.default.createElement("h3", null, t("runtime")), /* @__PURE__ */ import_react.default.createElement("dl", null, /* @__PURE__ */ import_react.default.createElement("dt", null, t("projectRoot")), /* @__PURE__ */ import_react.default.createElement("dd", null, /* @__PURE__ */ import_react.default.createElement("code", null, snapshot?.config?.projectRoot ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("dt", null, t("jobsDir")), /* @__PURE__ */ import_react.default.createElement("dd", null, /* @__PURE__ */ import_react.default.createElement("code", null, snapshot?.config?.jobsDir ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("dt", null, t("dshVersion")), /* @__PURE__ */ import_react.default.createElement("dd", null, snapshot?.config?.dshVersion ?? "\u2014"), /* @__PURE__ */ import_react.default.createElement("dt", null, t("agentImport")), /* @__PURE__ */ import_react.default.createElement("dd", null, /* @__PURE__ */ import_react.default.createElement("code", null, snapshot?.config?.agentImportPath ?? "\u2014")), /* @__PURE__ */ import_react.default.createElement("dt", null, t("pluginImport")), /* @__PURE__ */ import_react.default.createElement("dd", null, /* @__PURE__ */ import_react.default.createElement("code", null, snapshot?.config?.pluginImportPath ?? "\u2014")))), /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-skill-guide" }, /* @__PURE__ */ import_react.default.createElement("h3", null, t("skillGuide")), /* @__PURE__ */ import_react.default.createElement("p", null, t("skillBody"))))));
284
+ return /* @__PURE__ */ import_react.default.createElement("main", { className: "hse-root" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-settings" }, /* @__PURE__ */ import_react.default.createElement("h2", null, t("setupDoctor")), /* @__PURE__ */ import_react.default.createElement("p", null, t("setupHint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-checks" }, Object.entries(state.value?.checks ?? {}).map(([key, check]) => /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-check", key }, /* @__PURE__ */ import_react.default.createElement("b", { className: check.status === "ok" ? "hse-valid" : "hse-invalid" }, key, " \xB7 ", check.status), /* @__PURE__ */ import_react.default.createElement("small", null, check.detail))))));
339
285
  }
340
- function ToolField({ label, value, code = false }) {
341
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-tool-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, label), code ? /* @__PURE__ */ import_react.default.createElement("code", { title: String(value ?? "\u2014") }, String(value ?? "\u2014")) : /* @__PURE__ */ import_react.default.createElement("strong", null, String(value ?? "\u2014")));
286
+ function blockText(block) {
287
+ return isRecord(block) && Array.isArray(block.content) ? block.content.filter((item) => item?.type === "text").map((item) => item.text).join("\n") : "";
342
288
  }
343
- function toolPresentation(toolName, value, t) {
344
- if (toolName === "harbor_candidate_snapshot") {
345
- return { title: t("toolSnapshot"), summary: value ? `${value.candidate_id ?? "\u2014"} \xB7 ${value.version ?? "\u2014"}` : void 0, fields: [[t("candidate"), value?.candidate_id], [t("version"), value?.version], [t("digest"), shortDigest(value?.digest), true]] };
346
- }
347
- if (toolName === "harbor_eval_run") {
348
- const summary = value?.summary;
349
- return { title: t("toolRun"), summary: summary?.job ?? value?.jobDir, metrics: summary?.metrics, fields: [[t("job"), summary?.job ?? value?.jobDir, true], [t("trials"), summary?.n_trials], [t("exceptions"), summary?.n_exceptions], [t("digest"), shortDigest(value?.manifest?.digest), true]] };
350
- }
351
- if (toolName === "harbor_eval_result") {
352
- return { title: t("toolResult"), summary: value?.job, metrics: value?.metrics, fields: [[t("job"), value?.job, true], [t("trials"), value?.n_trials], [t("exceptions"), value?.n_exceptions], [t("context"), shortDigest(value?.evaluation_context?.digest), true]] };
289
+ function decodeToolResult(block) {
290
+ if (!isRecord(block) || block.isError) return void 0;
291
+ if (isRecord(block.meta)) return block.meta;
292
+ try {
293
+ const value = JSON.parse(blockText(block));
294
+ return isRecord(value) ? value : void 0;
295
+ } catch {
296
+ return void 0;
353
297
  }
354
- return { title: t("toolCompare"), summary: value?.decision, metrics: value?.candidate_metrics, fields: [[t("decision"), value?.decision], [t("candidate"), value?.candidate?.version ?? value?.candidate_job], [t("comparable"), value?.baseline_evaluation_context?.digest === value?.candidate_evaluation_context?.digest ? "\u2713" : "\u2014"], [t("changed"), value?.baseline_candidate?.digest !== value?.candidate?.digest ? "\u2713" : "\u2014"]], reasons: value?.reasons };
355
298
  }
356
- function HarborToolView({ block, toolName, t }) {
299
+ function HarborToolView({ block, toolName }) {
357
300
  const [open, setOpen] = (0, import_react.useState)(true);
358
- const running = !isRecord(block) || !("kind" in block);
359
- const failed = !running && block.isError;
360
301
  const value = decodeToolResult(block);
361
- const view = toolPresentation(toolName, value, t);
362
- return /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-tool", "data-state": running ? "running" : failed ? "error" : "success" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "hse-tool-head", type: "button", "aria-expanded": open, onClick: () => setOpen((current) => !current) }, /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-tool-icon" }, /* @__PURE__ */ import_react.default.createElement(WhaleIcon, null)), /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-tool-title" }, view.title), view.summary ? /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-tool-summary" }, "\xB7 ", view.summary) : null, /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-tool-state" }, running ? t("running") : failed ? t("toolFailed") : "\u2713")), open ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-tool-body" }, failed ? /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-tool-summary" }, blockText(block) || t("toolFailed")) : !value ? /* @__PURE__ */ import_react.default.createElement("span", { className: "hse-tool-summary" }, running ? t("running") : t("noResult")) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-tool-grid" }, view.fields?.map(([label, fieldValue, code], index) => /* @__PURE__ */ import_react.default.createElement(ToolField, { label, value: fieldValue, code, key: `${label}-${index}` }))), view.metrics ? /* @__PURE__ */ import_react.default.createElement("div", { className: "hse-metrics" }, /* @__PURE__ */ import_react.default.createElement(MetricPills, { metrics: view.metrics, t })) : null, Array.isArray(view.reasons) && view.reasons.length ? /* @__PURE__ */ import_react.default.createElement("ul", { className: "hse-reasons" }, view.reasons.map((reason, index) => /* @__PURE__ */ import_react.default.createElement("li", { key: index }, reason))) : null)) : null);
302
+ const running = !isRecord(block) || !("kind" in block);
303
+ return /* @__PURE__ */ import_react.default.createElement("section", { className: "hse-tool" }, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", onClick: () => setOpen(!open) }, /* @__PURE__ */ import_react.default.createElement("strong", null, "\u{1F433} ", toolName), /* @__PURE__ */ import_react.default.createElement("small", null, running ? "running" : block.isError ? "error" : "\u2713")), open ? /* @__PURE__ */ import_react.default.createElement("pre", null, value ? JSON.stringify(value, null, 2) : blockText(block) || "Running\u2026") : null);
363
304
  }
364
305
  var name = "dsh-harbor-evolution";
365
306
  var inject = ["slots", "locale"];
@@ -368,25 +309,10 @@ function apply(ctx) {
368
309
  ctx.effect(() => ctx.locale.register(NS, dictionaries), "harbor-evolution: locale");
369
310
  const t = ctx.locale.bind(NS);
370
311
  const injected = () => ({ t });
371
- ctx.slots.inject("conversation.view", () => ctx.slots.register({
372
- name: "conversation.view",
373
- id: "harbor-evolution",
374
- order: 30,
375
- locale: NS,
376
- label: () => t("tab"),
377
- inject: injected
378
- }, DashboardView));
379
- ctx.slots.inject("settings.section", () => ctx.slots.register({
380
- name: "settings.section",
381
- id: "harbor-evolution",
382
- order: 35,
383
- label: () => t("settings"),
384
- inject: injected
385
- }, DoctorView));
312
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({ name: "conversation.view", id: "harbor-evolution", order: 30, locale: NS, label: () => t("tab"), inject: injected }, DashboardView));
313
+ ctx.slots.inject("settings.section", () => ctx.slots.register({ name: "settings.section", id: "harbor-evolution", order: 35, label: () => t("settings"), inject: injected }, DoctorView));
386
314
  ctx.slots.inject("tool.call.toolview", function* registerTools() {
387
- for (const key of ["harbor_candidate_snapshot", "harbor_eval_run", "harbor_eval_result", "harbor_candidate_compare"]) {
388
- yield ctx.slots.register({ name: "tool.call.toolview", key, inject: injected }, HarborToolView);
389
- }
315
+ for (const key of ["harbor_candidate_snapshot", "harbor_evolution_init", "harbor_evolution_doctor", "harbor_dataset_validate", "harbor_context_preview", "harbor_eval_run", "harbor_eval_result", "harbor_candidate_compare"]) yield ctx.slots.register({ name: "tool.call.toolview", key, inject: injected }, HarborToolView);
390
316
  });
391
317
  }
392
318
  module.exports = { name, inject, apply };