@ran-sh/dsh-crew 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -35,6 +35,7 @@ const SETTINGS_SECTION_IDS = Object.freeze([
35
35
  "flash",
36
36
  "pro",
37
37
  "dispatch",
38
+ "adaptive",
38
39
  "runtime",
39
40
  "multimodal",
40
41
  "providers",
@@ -70,17 +71,276 @@ function openSections(state, ids) {
70
71
  return next;
71
72
  }
72
73
  //#endregion
74
+ //#region src/client/activation-summary.tsx
75
+ const ORDER = [
76
+ "live",
77
+ "next-workflow",
78
+ "next-session",
79
+ "restart-required"
80
+ ];
81
+ const LABELS = {
82
+ zh: {
83
+ title: "配置生效边界",
84
+ hint: "这里显示全局 Settings 保存后的实际生效时机;会话内 dsh_worker_config 覆盖可能更早生效。",
85
+ boundary: {
86
+ live: "Live · 当前运行时",
87
+ "next-workflow": "Next workflow · 下一个任务",
88
+ "next-session": "Next session · 新 CC / Codex 会话",
89
+ "restart-required": "Restart required · 重启 DSH / MCP"
90
+ }
91
+ },
92
+ en: {
93
+ title: "Configuration activation boundaries",
94
+ hint: "Shows when persisted Settings changes actually take effect. Session-level dsh_worker_config overrides may activate earlier.",
95
+ boundary: {
96
+ live: "Live · current runtime",
97
+ "next-workflow": "Next workflow",
98
+ "next-session": "Next session · new CC / Codex session",
99
+ "restart-required": "Restart required · restart DSH / MCP"
100
+ }
101
+ }
102
+ };
103
+ function groupActivationBoundaries(activation = {}) {
104
+ const grouped = Object.fromEntries(ORDER.map((boundary) => [boundary, []]));
105
+ for (const [key, entry] of Object.entries(activation)) if (entry?.global && grouped[entry.global]) grouped[entry.global].push(key);
106
+ for (const boundary of ORDER) grouped[boundary].sort();
107
+ return grouped;
108
+ }
109
+ function ActivationSummary({ activation, locale }) {
110
+ if (!activation || Object.keys(activation).length === 0) return null;
111
+ const copy = LABELS[locale === "zh" ? "zh" : "en"];
112
+ const grouped = groupActivationBoundaries(activation);
113
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
114
+ style: {
115
+ border: "1px solid rgba(128,128,128,0.22)",
116
+ borderRadius: 8,
117
+ padding: "9px 12px",
118
+ display: "flex",
119
+ flexDirection: "column",
120
+ gap: 5
121
+ },
122
+ children: [
123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
124
+ style: {
125
+ fontWeight: 600,
126
+ fontSize: 12.5
127
+ },
128
+ children: copy.title
129
+ }),
130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
131
+ style: {
132
+ fontSize: 11,
133
+ opacity: .6
134
+ },
135
+ children: copy.hint
136
+ }),
137
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
138
+ style: {
139
+ display: "grid",
140
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
141
+ gap: "5px 12px"
142
+ },
143
+ children: ORDER.map((boundary) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
144
+ style: { minWidth: 0 },
145
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
146
+ style: {
147
+ fontSize: 10.5,
148
+ opacity: .7,
149
+ fontWeight: 600
150
+ },
151
+ children: copy.boundary[boundary]
152
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
153
+ style: {
154
+ fontSize: 10.5,
155
+ opacity: .55,
156
+ wordBreak: "break-word"
157
+ },
158
+ children: grouped[boundary].length ? grouped[boundary].join(" · ") : "—"
159
+ })]
160
+ }, boundary))
161
+ })
162
+ ]
163
+ });
164
+ }
165
+ //#endregion
166
+ //#region src/client/host-readiness.mjs
167
+ const READINESS_STATES = Object.freeze({
168
+ READY: "READY",
169
+ DEGRADED: "DEGRADED",
170
+ UNAVAILABLE: "UNAVAILABLE",
171
+ UNKNOWN: "UNKNOWN"
172
+ });
173
+ function componentState(status, keys, { aligned = false } = {}) {
174
+ if (status === void 0 || status === null || typeof status !== "object") return READINESS_STATES.UNKNOWN;
175
+ if (status.installed === false) return READINESS_STATES.UNAVAILABLE;
176
+ if (status.installed !== true || !status.components || typeof status.components !== "object") return READINESS_STATES.UNKNOWN;
177
+ const evidence = keys.map((key) => status.components[key]);
178
+ if (evidence.some((value) => typeof value !== "boolean")) return READINESS_STATES.UNKNOWN;
179
+ return evidence.every(Boolean) && (!aligned || status.components.target_alignment === true) ? READINESS_STATES.READY : READINESS_STATES.DEGRADED;
180
+ }
181
+ function runtimeState(runtime) {
182
+ if (runtime === void 0) return READINESS_STATES.UNKNOWN;
183
+ if (runtime === null) return READINESS_STATES.UNAVAILABLE;
184
+ return runtime?.ok === true && runtime.service === "dsh-crew-hub" ? READINESS_STATES.READY : READINESS_STATES.DEGRADED;
185
+ }
186
+ function bridgeState(surface) {
187
+ if (surface === "official-bridge") return READINESS_STATES.READY;
188
+ if (surface === "native-crew-harness") return READINESS_STATES.UNAVAILABLE;
189
+ return READINESS_STATES.UNKNOWN;
190
+ }
191
+ /** Project only structured, non-secret installer/runtime evidence. */
192
+ function projectHostReadiness({ installStatus, runtime, surface } = {}) {
193
+ const codex = installStatus?.codex;
194
+ const claude = installStatus?.claude;
195
+ return [
196
+ {
197
+ id: "codex_mcp",
198
+ state: componentState(codex, ["mcp"], { aligned: true })
199
+ },
200
+ {
201
+ id: "ds_worker",
202
+ state: componentState(codex, ["worker_role"], { aligned: true })
203
+ },
204
+ {
205
+ id: "ds_reviewer",
206
+ state: componentState(codex, ["reviewer_role"], { aligned: true })
207
+ },
208
+ {
209
+ id: "claude_plugin",
210
+ state: componentState(claude, [
211
+ "enabled",
212
+ "marketplace",
213
+ "snapshot",
214
+ "permissions"
215
+ ])
216
+ },
217
+ {
218
+ id: "crew_harness",
219
+ state: runtimeState(runtime),
220
+ detail: runtime?.runtime_version ?? null
221
+ },
222
+ {
223
+ id: "official_bridge",
224
+ state: bridgeState(surface)
225
+ }
226
+ ];
227
+ }
228
+ //#endregion
229
+ //#region src/client/surface-detection.mjs
230
+ const CREW_UI_SURFACES = Object.freeze({
231
+ OFFICIAL: "official-bridge",
232
+ NATIVE: "native-crew-harness",
233
+ UNKNOWN: "unknown"
234
+ });
235
+ /**
236
+ * Classify the current same-origin UI from explicit backend contracts. The
237
+ * official bridge signal wins because its proxied runtime response correctly
238
+ * describes the 3210 backend, not the browser-facing 3080 surface.
239
+ */
240
+ function classifyCrewSurface({ bridgeStatus, runtime } = {}) {
241
+ if (bridgeStatus?.ok === true && (bridgeStatus.surface === CREW_UI_SURFACES.OFFICIAL || bridgeStatus.mode === "official-3080-isolated-3210")) return CREW_UI_SURFACES.OFFICIAL;
242
+ if (runtime?.ok === true && runtime.service === "dsh-crew-hub" && (runtime.surface === CREW_UI_SURFACES.NATIVE || runtime.surface === void 0)) return CREW_UI_SURFACES.NATIVE;
243
+ return CREW_UI_SURFACES.UNKNOWN;
244
+ }
245
+ function surfaceResponsibilities(surface) {
246
+ const fullControlPlane = surface === CREW_UI_SURFACES.OFFICIAL;
247
+ return {
248
+ fullControlPlane,
249
+ minimalDiagnostics: !fullControlPlane
250
+ };
251
+ }
252
+ //#endregion
253
+ //#region src/client/task-telemetry.mjs
254
+ function clean(value, fallback) {
255
+ return (typeof value === "string" ? value.trim() : "") || fallback;
256
+ }
257
+ function validTimestamp(value) {
258
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) return null;
259
+ return value;
260
+ }
261
+ function sorted(values) {
262
+ return [...values].sort((left, right) => left.localeCompare(right));
263
+ }
264
+ function hasInvocationEvidence(job) {
265
+ const tokenCount = Number(job?.tokens?.input ?? 0) + Number(job?.tokens?.output ?? 0);
266
+ return Number(job?.turn ?? 0) > 0 || Number(job?.toolCalls ?? 0) > 0 || tokenCount > 0;
267
+ }
268
+ /**
269
+ * Build a bounded, presentation-only model activity summary from the jobs the
270
+ * Hub already exposes. No prompts, results, credentials, or new persistence
271
+ * are introduced here.
272
+ */
273
+ function aggregateModelInvocations(jobs = []) {
274
+ const groups = /* @__PURE__ */ new Map();
275
+ const recentJobs = Array.isArray(jobs) ? jobs.slice(-500) : [];
276
+ for (const job of recentJobs) {
277
+ const provider = clean(job?.provider, "");
278
+ const model = clean(job?.model, "");
279
+ if (!provider || !model || !hasInvocationEvidence(job)) continue;
280
+ const key = `${provider}\0${model}`;
281
+ const current = groups.get(key) ?? {
282
+ provider,
283
+ model,
284
+ count: 0,
285
+ taskSources: /* @__PURE__ */ new Set(),
286
+ selectionSources: /* @__PURE__ */ new Set(),
287
+ roles: /* @__PURE__ */ new Set(),
288
+ lastCalledAt: null
289
+ };
290
+ current.count += 1;
291
+ current.taskSources.add(clean(job?.source, "api"));
292
+ current.selectionSources.add(clean(job?.selection_source, "unknown"));
293
+ current.roles.add(job?.role === "reviewer" ? "reviewer" : "worker");
294
+ const calledAt = validTimestamp(job?.startedAt);
295
+ if (calledAt && (!current.lastCalledAt || Date.parse(calledAt) > Date.parse(current.lastCalledAt))) current.lastCalledAt = calledAt;
296
+ groups.set(key, current);
297
+ }
298
+ return [...groups.values()].sort((left, right) => {
299
+ return (right.lastCalledAt ? Date.parse(right.lastCalledAt) : -1) - (left.lastCalledAt ? Date.parse(left.lastCalledAt) : -1) || `${left.provider}/${left.model}`.localeCompare(`${right.provider}/${right.model}`);
300
+ }).slice(0, 50).map((entry) => ({
301
+ provider: entry.provider,
302
+ model: entry.model,
303
+ count: entry.count,
304
+ task_sources: sorted(entry.taskSources),
305
+ selection_sources: sorted(entry.selectionSources),
306
+ roles: sorted(entry.roles),
307
+ last_called_at: entry.lastCalledAt
308
+ }));
309
+ }
310
+ //#endregion
73
311
  //#region src/client/index.tsx
74
312
  const inject$1 = ["slots", "locale"];
75
- const API$1 = "/_dsh/dsh-crew";
313
+ const API = "/_dsh/dsh-crew";
314
+ const CREW_HARNESS_URL = "http://127.0.0.1:3210/";
315
+ const CREW_CONTROL_PLANE_URL = "http://127.0.0.1:3080/";
316
+ const DEFAULT_ADAPTIVE = {
317
+ enabled: false,
318
+ window_size: 8,
319
+ min_samples: 2
320
+ };
321
+ function clampInt(value, fallback, min, max) {
322
+ const parsed = Number(value);
323
+ if (!Number.isInteger(parsed)) return fallback;
324
+ return Math.max(min, Math.min(max, parsed));
325
+ }
326
+ function normalizeAdaptive(value) {
327
+ const windowSize = clampInt(value?.window_size, DEFAULT_ADAPTIVE.window_size, 1, 32);
328
+ return {
329
+ enabled: value?.enabled === true,
330
+ window_size: windowSize,
331
+ min_samples: clampInt(value?.min_samples, DEFAULT_ADAPTIVE.min_samples, 1, windowSize)
332
+ };
333
+ }
76
334
  const COPY = {
77
335
  zh: {
78
336
  label: "DSH Crew",
79
337
  title: "DSH Crew",
80
- intro: " Claude Code / Codex 的子任务派给本实例的 DSH agent,在宿主里显示为原生子代理、进度可见。配置派发默认值、执行方式,以及接入视觉与生图能力,一键装进宿主。",
338
+ intro: "日常 Crew 控制台:在一个入口里配置编排、角色、模型路由与宿主集成,并跟踪真实 Worker / Reviewer 任务。",
81
339
  integrations: "集成",
82
340
  installed: "已安装",
83
341
  notInstalled: "未安装",
342
+ ready: "可调用",
343
+ notReady: "未就绪",
84
344
  hud: "HUD 段",
85
345
  install: "安装",
86
346
  update: "更新",
@@ -94,15 +354,34 @@ const COPY = {
94
354
  jobCount: (count) => `${count} 个任务`,
95
355
  runningCount: (count) => `${count} 个运行中`,
96
356
  sectionNames: {
97
- integrations: "宿主集成",
98
- workflow: "工作流",
99
- flash: "Flash",
100
- pro: "Pro",
101
- dispatch: "派发策略",
102
- runtime: "运行连接",
357
+ integrations: "Codex / Claude 集成状态",
358
+ workflow: "Crew 工作流设置",
359
+ flash: "Worker / Flash",
360
+ pro: "Reviewer / Pro",
361
+ dispatch: "模型优先级与派发",
362
+ adaptive: "自适应路由",
363
+ runtime: "运行 / 生效边界",
103
364
  multimodal: "视觉与生图",
104
365
  providers: "自定义 Provider",
105
- jobs: "Worker 任务"
366
+ jobs: "任务状态"
367
+ },
368
+ openHarness: "打开 3210 Crew Harness →",
369
+ harnessHint: "底层 Provider、Harness Models 与运行时配置",
370
+ hostReadiness: "宿主集成就绪度",
371
+ hostReadinessHint: "只使用结构化安装与运行时证据;缺少证据不会显示 READY。",
372
+ readinessLabels: {
373
+ codex_mcp: "Codex MCP",
374
+ ds_worker: "ds-worker",
375
+ ds_reviewer: "ds-reviewer",
376
+ claude_plugin: "Claude plugin",
377
+ crew_harness: "Crew Harness",
378
+ official_bridge: "Official bridge"
379
+ },
380
+ readinessStates: {
381
+ READY: "READY",
382
+ DEGRADED: "DEGRADED",
383
+ UNAVAILABLE: "UNAVAILABLE",
384
+ UNKNOWN: "UNKNOWN"
106
385
  },
107
386
  globalHint: "修改即时保存到 ~/.config/dsh-crew/config.json;CC / Codex 的新会话自动读取为默认值(会话内可用 /dsh-crew:config 临时覆盖)。",
108
387
  orchestration: "Agent 编排",
@@ -284,10 +563,28 @@ const COPY = {
284
563
  save: "保存",
285
564
  saved: "已保存",
286
565
  jobs: "Worker 任务",
287
- empty: "当前没有 worker 任务。",
566
+ empty: "当前没有 Worker / Reviewer 任务。",
567
+ adaptiveTitle: "自适应模型路由(实验)",
568
+ adaptiveHint: "默认关闭;只重排系统自动候选,手动模型优先级始终保持原序。健康信号仅来自本进程已观察到的成功、失败、超时与粗粒度延迟,重启后清空。",
569
+ adaptiveEnabled: "启用自适应路由",
570
+ adaptiveWindow: "健康窗口",
571
+ adaptiveSamples: "最少样本",
572
+ adaptiveBoundary: "下一个工作流生效",
573
+ modelActivity: "模型调用概览",
574
+ modelActivityHint: "聚合本 Hub 内存中最近 500 个任务的真实调用证据,最多显示 50 个模型;不保存提示词、结果或凭据。",
575
+ noModelActivity: "还没有真实模型调用。",
576
+ calls: "调用次数",
577
+ invocationSource: "任务来源",
578
+ routingSource: "路由来源",
579
+ lastCalled: "最近调用",
580
+ role: "角色",
581
+ model: "模型",
582
+ never: "—",
288
583
  col: {
289
584
  id: "任务",
585
+ role: "角色",
290
586
  source: "来源",
587
+ model: "模型",
291
588
  tier: "档位",
292
589
  status: "状态",
293
590
  progress: "进度",
@@ -311,10 +608,12 @@ const COPY = {
311
608
  en: {
312
609
  label: "DSH Crew",
313
610
  title: "DSH Crew",
314
- intro: "Dispatch Claude Code / Codex subtasks to DSH agents within this instance, displayed as native subagents in the host with live progress. Configure dispatch defaults, execution mode, and vision/image generation (via subscription CLI or your own OpenAI API), then one-click install into the host.",
611
+ intro: "The daily Crew console: configure orchestration, roles, model routing, and host integrations in one place while tracking real Worker / Reviewer jobs.",
315
612
  integrations: "Integrations",
316
613
  installed: "installed",
317
614
  notInstalled: "not installed",
615
+ ready: "ready",
616
+ notReady: "not ready",
318
617
  hud: "HUD segment",
319
618
  install: "Install",
320
619
  update: "Update",
@@ -328,15 +627,34 @@ const COPY = {
328
627
  jobCount: (count) => `${count} jobs`,
329
628
  runningCount: (count) => `${count} running`,
330
629
  sectionNames: {
331
- integrations: "Host integrations",
332
- workflow: "Workflow",
333
- flash: "Flash",
334
- pro: "Pro",
335
- dispatch: "Dispatch policy",
336
- runtime: "Runtime connection",
630
+ integrations: "Codex / Claude integration status",
631
+ workflow: "Crew workflow settings",
632
+ flash: "Worker / Flash",
633
+ pro: "Reviewer / Pro",
634
+ dispatch: "Model priority & dispatch",
635
+ adaptive: "Adaptive routing",
636
+ runtime: "Runtime / activation boundaries",
337
637
  multimodal: "Vision & image generation",
338
638
  providers: "Custom providers",
339
- jobs: "Worker jobs"
639
+ jobs: "Task status"
640
+ },
641
+ openHarness: "Open 3210 Crew Harness →",
642
+ harnessHint: "Low-level providers, Harness Models, and runtime configuration",
643
+ hostReadiness: "Host integration readiness",
644
+ hostReadinessHint: "Uses structured installer and runtime evidence only; missing evidence is never READY.",
645
+ readinessLabels: {
646
+ codex_mcp: "Codex MCP",
647
+ ds_worker: "ds-worker",
648
+ ds_reviewer: "ds-reviewer",
649
+ claude_plugin: "Claude plugin",
650
+ crew_harness: "Crew Harness",
651
+ official_bridge: "Official bridge"
652
+ },
653
+ readinessStates: {
654
+ READY: "READY",
655
+ DEGRADED: "DEGRADED",
656
+ UNAVAILABLE: "UNAVAILABLE",
657
+ UNKNOWN: "UNKNOWN"
340
658
  },
341
659
  globalHint: "Changes save instantly to ~/.config/dsh-crew/config.json; new CC / Codex sessions pick them up as defaults (override per session with /dsh-crew:config).",
342
660
  orchestration: "Agent orchestration",
@@ -518,10 +836,28 @@ const COPY = {
518
836
  save: "Save",
519
837
  saved: "Saved",
520
838
  jobs: "Worker jobs",
521
- empty: "No worker jobs yet.",
839
+ empty: "No Worker / Reviewer jobs yet.",
840
+ adaptiveTitle: "Adaptive model routing (experimental)",
841
+ adaptiveHint: "Off by default. Only system-derived candidates may be reordered; explicit model priorities always keep their order. Health uses only process-local success, failure, timeout, and coarse latency observations and resets on restart.",
842
+ adaptiveEnabled: "Enable adaptive routing",
843
+ adaptiveWindow: "Health window",
844
+ adaptiveSamples: "Minimum samples",
845
+ adaptiveBoundary: "Effective for the next workflow",
846
+ modelActivity: "Model invocation overview",
847
+ modelActivityHint: "Aggregates real invocation evidence from the latest 500 in-memory Hub jobs and shows at most 50 models; prompts, results, and credentials are never stored here.",
848
+ noModelActivity: "No real model invocations yet.",
849
+ calls: "Calls",
850
+ invocationSource: "Task source",
851
+ routingSource: "Routing source",
852
+ lastCalled: "Last called",
853
+ role: "Role",
854
+ model: "Model",
855
+ never: "—",
522
856
  col: {
523
857
  id: "job",
858
+ role: "role",
524
859
  source: "source",
860
+ model: "model",
525
861
  tier: "tier",
526
862
  status: "status",
527
863
  progress: "progress",
@@ -873,6 +1209,95 @@ function elapsed(startedAt, endedAt) {
873
1209
  function ktok(n) {
874
1210
  return n >= 1e3 ? `${Math.round(n / 100) / 10}k` : `${n}`;
875
1211
  }
1212
+ function formatTimestamp(value, locale) {
1213
+ if (!value || !Number.isFinite(Date.parse(value))) return "—";
1214
+ return new Intl.DateTimeFormat(locale === "zh" ? "zh-CN" : "en", {
1215
+ month: "2-digit",
1216
+ day: "2-digit",
1217
+ hour: "2-digit",
1218
+ minute: "2-digit",
1219
+ second: "2-digit"
1220
+ }).format(new Date(value));
1221
+ }
1222
+ function readinessChip(state) {
1223
+ const color = state === READINESS_STATES.READY ? "#3fb950" : state === READINESS_STATES.DEGRADED ? "#c98735" : state === READINESS_STATES.UNAVAILABLE ? "#f85149" : "inherit";
1224
+ return {
1225
+ fontSize: 10.5,
1226
+ fontWeight: 650,
1227
+ padding: "1px 7px",
1228
+ borderRadius: 99,
1229
+ border: `1px solid ${state === READINESS_STATES.UNKNOWN ? "rgba(128,128,128,0.35)" : color}`,
1230
+ color,
1231
+ opacity: state === READINESS_STATES.UNKNOWN ? .58 : 1
1232
+ };
1233
+ }
1234
+ function MinimalCrewPanel({ locale, surface, runtime }) {
1235
+ const zh = locale === "zh";
1236
+ const native = surface === CREW_UI_SURFACES.NATIVE;
1237
+ const title = native ? zh ? "DSH Crew Runtime" : "DSH Crew Runtime" : zh ? "DSH Crew Surface 未确认" : "DSH Crew surface not verified";
1238
+ const hint = native ? zh ? "这里是隔离的 3210 Crew Harness。请使用 Harness 原生菜单管理 Provider、Harness Models、Agent 预设与底层配置;Crew 编排和宿主集成统一在 3080 管理。" : "This is the isolated 3210 Crew Harness. Use the native Harness menus for Providers, Harness Models, Agent presets, and low-level settings; manage Crew orchestration and host integrations on 3080." : zh ? "无法用结构化后端证据确认当前界面,因此已保守隐藏 Crew 编排、任务与宿主集成功能。" : "Structured backend evidence could not identify this surface, so Crew orchestration, jobs, and host integrations are conservatively hidden.";
1239
+ const runtimeReady = runtime?.ok === true && runtime?.service === "dsh-crew-hub";
1240
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1241
+ style: {
1242
+ display: "flex",
1243
+ flexDirection: "column",
1244
+ gap: 10,
1245
+ fontSize: 13,
1246
+ lineHeight: 1.55
1247
+ },
1248
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1249
+ style: {
1250
+ ...S.block,
1251
+ background: "linear-gradient(135deg, rgba(74,158,255,0.08), rgba(128,128,128,0.02))"
1252
+ },
1253
+ children: [
1254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1255
+ style: {
1256
+ fontSize: 19,
1257
+ fontWeight: 680
1258
+ },
1259
+ children: title
1260
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1261
+ style: {
1262
+ fontSize: 12,
1263
+ opacity: .68,
1264
+ marginTop: 3
1265
+ },
1266
+ children: hint
1267
+ })] }),
1268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1269
+ style: {
1270
+ display: "flex",
1271
+ gap: 7,
1272
+ flexWrap: "wrap"
1273
+ },
1274
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1275
+ style: readinessChip(runtimeReady ? READINESS_STATES.READY : READINESS_STATES.UNKNOWN),
1276
+ children: runtimeReady ? `Runtime ${runtime.runtime_version ?? "READY"}` : "Runtime UNKNOWN"
1277
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1278
+ style: readinessChip(native ? READINESS_STATES.READY : READINESS_STATES.UNKNOWN),
1279
+ children: native ? "3210 · isolated" : "surface · unknown"
1280
+ })]
1281
+ }),
1282
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1283
+ href: CREW_CONTROL_PLANE_URL,
1284
+ target: "_blank",
1285
+ rel: "noopener noreferrer",
1286
+ style: {
1287
+ ...S.btn,
1288
+ alignSelf: "flex-start",
1289
+ textDecoration: "none",
1290
+ fontWeight: 650,
1291
+ padding: "7px 12px",
1292
+ borderColor: "rgba(74,158,255,0.55)",
1293
+ background: "rgba(74,158,255,0.08)"
1294
+ },
1295
+ children: zh ? "打开 3080 DSH Crew 控制台 →" : "Open the 3080 DSH Crew console →"
1296
+ })
1297
+ ]
1298
+ })
1299
+ });
1300
+ }
876
1301
  function WorkersPanel({ ctx }) {
877
1302
  const locale = (0, react.useSyncExternalStore)((notify) => ctx.on("locale/change", notify), () => ctx.locale.getLocale().active, () => ctx.locale.getLocale().active);
878
1303
  const copy = COPY[locale === "zh" ? "zh" : "en"];
@@ -893,6 +1318,8 @@ function WorkersPanel({ ctx }) {
893
1318
  const [modelQuery, setModelQuery] = (0, react.useState)("");
894
1319
  const [testResult, setTestResult] = (0, react.useState)(null);
895
1320
  const [expandedSections, setExpandedSections] = (0, react.useState)(() => readSectionState(sectionStorage()));
1321
+ const [surface, setSurface] = (0, react.useState)("detecting");
1322
+ const [runtimeInfo, setRuntimeInfo] = (0, react.useState)(void 0);
896
1323
  const toggleSection = (sectionId) => {
897
1324
  setExpandedSections((current) => ({
898
1325
  ...current,
@@ -933,7 +1360,7 @@ function WorkersPanel({ ctx }) {
933
1360
  }, [copy]);
934
1361
  /** Every request carries the active locale: the panel is the authority on it
935
1362
  * (DSH's setting may be unset), and the hub localizes its own strings from it. */
936
- const withLang = (0, react.useCallback)((path) => `${API$1}${path}${path.includes("?") ? "&" : "?"}lang=${locale === "zh" ? "zh" : "en"}`, [locale]);
1363
+ const withLang = (0, react.useCallback)((path) => `${API}${path}${path.includes("?") ? "&" : "?"}lang=${locale === "zh" ? "zh" : "en"}`, [locale]);
937
1364
  const get = (0, react.useCallback)(async (path) => readJson(await fetch(withLang(path), { cache: "no-store" }), path), [readJson, withLang]);
938
1365
  const post = (0, react.useCallback)(async (path, body) => readJson(await fetch(withLang(path), {
939
1366
  method: "POST",
@@ -947,6 +1374,27 @@ function WorkersPanel({ ctx }) {
947
1374
  withLang,
948
1375
  locale
949
1376
  ]);
1377
+ const detectSurface = (0, react.useCallback)(async () => {
1378
+ const readOptional = async (path) => {
1379
+ try {
1380
+ const response = await fetch(`${API}${path}`, { cache: "no-store" });
1381
+ if (!response.ok) return null;
1382
+ const body = await response.json();
1383
+ return body && typeof body === "object" ? body : null;
1384
+ } catch {
1385
+ return null;
1386
+ }
1387
+ };
1388
+ const [bridgeStatus, runtime] = await Promise.all([readOptional("/bridge-status"), readOptional("/runtime")]);
1389
+ setRuntimeInfo(runtime);
1390
+ setSurface(classifyCrewSurface({
1391
+ bridgeStatus,
1392
+ runtime
1393
+ }));
1394
+ }, []);
1395
+ (0, react.useEffect)(() => {
1396
+ detectSurface();
1397
+ }, [detectSurface]);
950
1398
  const refreshAll = (0, react.useCallback)(async () => {
951
1399
  try {
952
1400
  const [j, s, c, pr] = await Promise.all([
@@ -968,6 +1416,7 @@ function WorkersPanel({ ctx }) {
968
1416
  } catch {}
969
1417
  }, [get]);
970
1418
  (0, react.useEffect)(() => {
1419
+ if (surface !== CREW_UI_SURFACES.OFFICIAL) return void 0;
971
1420
  refreshAll();
972
1421
  const timer = setInterval(() => {
973
1422
  get("/jobs").then((j) => {
@@ -975,7 +1424,11 @@ function WorkersPanel({ ctx }) {
975
1424
  }).catch(() => {});
976
1425
  }, 3e3);
977
1426
  return () => clearInterval(timer);
978
- }, [refreshAll, get]);
1427
+ }, [
1428
+ surface,
1429
+ refreshAll,
1430
+ get
1431
+ ]);
979
1432
  const refreshHarnessModels = (0, react.useCallback)(async () => {
980
1433
  setModelCatalogBusy(true);
981
1434
  setModelCatalogError("");
@@ -990,8 +1443,8 @@ function WorkersPanel({ ctx }) {
990
1443
  }
991
1444
  }, [get, copy]);
992
1445
  (0, react.useEffect)(() => {
993
- refreshHarnessModels();
994
- }, [refreshHarnessModels]);
1446
+ if (surface === CREW_UI_SURFACES.OFFICIAL) refreshHarnessModels();
1447
+ }, [surface, refreshHarnessModels]);
995
1448
  const act = (0, react.useCallback)(async (target, confirmName) => {
996
1449
  if (confirmName && !window.confirm(copy.confirmRestore(confirmName))) return;
997
1450
  setBusy(true);
@@ -1012,15 +1465,27 @@ function WorkersPanel({ ctx }) {
1012
1465
  get,
1013
1466
  copy
1014
1467
  ]);
1015
- const applyPatch = (0, react.useCallback)((patch) => {
1016
- post("/config", patch).then((r) => {
1017
- if (r.ok) {
1018
- setConfig(r.config);
1019
- setNotice(copy.saved);
1020
- setTimeout(() => setNotice(""), 1500);
1021
- }
1022
- }).catch(() => {});
1023
- }, [post, copy]);
1468
+ const applyPatch = (0, react.useCallback)(async (patch) => {
1469
+ try {
1470
+ const result = await post("/config", patch);
1471
+ if (!result.ok) throw new Error(result.error ?? "Configuration save failed");
1472
+ setConfig(result.config);
1473
+ setNotice(copy.saved);
1474
+ setTimeout(() => setNotice(""), 1500);
1475
+ return true;
1476
+ } catch (error) {
1477
+ setNotice(String(error?.message ?? error));
1478
+ try {
1479
+ const authoritative = await get("/config");
1480
+ if (authoritative.ok) setConfig(authoritative.config);
1481
+ } catch {}
1482
+ return false;
1483
+ }
1484
+ }, [
1485
+ post,
1486
+ get,
1487
+ copy
1488
+ ]);
1024
1489
  /** Selects & checkboxes: apply immediately. */
1025
1490
  const field = (key, value) => {
1026
1491
  setConfig((c) => ({
@@ -1272,7 +1737,7 @@ function WorkersPanel({ ctx }) {
1272
1737
  children: fields
1273
1738
  }) : fields]
1274
1739
  });
1275
- const integrationRow = (name, installed, extra, installTarget, uninstallTarget, tips) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1740
+ const integrationRow = (name, installed, ready, extra, installTarget, uninstallTarget, tips) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1276
1741
  style: S.card,
1277
1742
  children: [
1278
1743
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1287,6 +1752,10 @@ function WorkersPanel({ ctx }) {
1287
1752
  style: S.chip(installed),
1288
1753
  children: installed ? `● ${copy.installed}` : `○ ${copy.notInstalled}`
1289
1754
  }),
1755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1756
+ style: S.chip(ready),
1757
+ children: ready ? `● ${copy.ready}` : `○ ${copy.notReady}`
1758
+ }),
1290
1759
  extra,
1291
1760
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
1292
1761
  !installed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -1321,6 +1790,37 @@ function WorkersPanel({ ctx }) {
1321
1790
  })
1322
1791
  ]
1323
1792
  });
1793
+ const adaptive = normalizeAdaptive(config?.worker?.model_policy?.adaptive);
1794
+ const setAdaptiveLocal = (candidate) => {
1795
+ const next = normalizeAdaptive(candidate);
1796
+ setConfig((current) => ({
1797
+ ...current,
1798
+ worker: {
1799
+ ...current.worker,
1800
+ model_policy: {
1801
+ ...current.worker?.model_policy,
1802
+ adaptive: next
1803
+ }
1804
+ }
1805
+ }));
1806
+ return next;
1807
+ };
1808
+ const saveAdaptive = (candidate) => {
1809
+ const next = setAdaptiveLocal(candidate);
1810
+ applyPatch({ worker: { model_policy: { adaptive: next } } });
1811
+ };
1812
+ const modelActivity = aggregateModelInvocations(jobs);
1813
+ const currentSurfaceResponsibilities = surfaceResponsibilities(surface);
1814
+ const hostReadiness = projectHostReadiness({
1815
+ installStatus: status,
1816
+ runtime: runtimeInfo,
1817
+ surface
1818
+ });
1819
+ if (!currentSurfaceResponsibilities.fullControlPlane) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MinimalCrewPanel, {
1820
+ locale,
1821
+ surface,
1822
+ runtime: runtimeInfo
1823
+ });
1324
1824
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1325
1825
  style: {
1326
1826
  display: "flex",
@@ -1330,17 +1830,90 @@ function WorkersPanel({ ctx }) {
1330
1830
  lineHeight: 1.55
1331
1831
  },
1332
1832
  children: [
1333
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1833
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1334
1834
  style: {
1335
- fontSize: 19,
1336
- fontWeight: 600,
1337
- marginBottom: -2
1835
+ border: "1px solid rgba(128,128,128,0.24)",
1836
+ borderRadius: 12,
1837
+ padding: "14px 15px",
1838
+ display: "flex",
1839
+ gap: 14,
1840
+ alignItems: "center",
1841
+ flexWrap: "wrap",
1842
+ background: "linear-gradient(135deg, rgba(74,158,255,0.10), rgba(128,128,128,0.025) 56%)"
1338
1843
  },
1339
- children: copy.title
1340
- }),
1341
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1342
- style: { opacity: .75 },
1343
- children: copy.intro
1844
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1845
+ style: {
1846
+ minWidth: 240,
1847
+ flex: 1
1848
+ },
1849
+ children: [
1850
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1851
+ style: {
1852
+ fontSize: 20,
1853
+ fontWeight: 680,
1854
+ letterSpacing: "-0.01em"
1855
+ },
1856
+ children: copy.title
1857
+ }),
1858
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1859
+ style: {
1860
+ opacity: .7,
1861
+ fontSize: 12.5,
1862
+ marginTop: 2
1863
+ },
1864
+ children: copy.intro
1865
+ }),
1866
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1867
+ style: {
1868
+ display: "flex",
1869
+ gap: 6,
1870
+ marginTop: 8,
1871
+ flexWrap: "wrap"
1872
+ },
1873
+ children: [
1874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1875
+ style: S.chip(!!status?.codex?.ready),
1876
+ children: ["Codex ", status?.codex?.ready ? "READY" : "CHECK"]
1877
+ }),
1878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1879
+ style: S.chip(!!status?.claude?.ready),
1880
+ children: ["Claude ", status?.claude?.ready ? "READY" : "CHECK"]
1881
+ }),
1882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1883
+ style: S.chip(jobs.some((job) => job.status === "running")),
1884
+ children: [jobs.filter((job) => job.status === "running").length, " running"]
1885
+ })
1886
+ ]
1887
+ })
1888
+ ]
1889
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1890
+ style: {
1891
+ display: "flex",
1892
+ flexDirection: "column",
1893
+ alignItems: "flex-end",
1894
+ gap: 3
1895
+ },
1896
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1897
+ href: CREW_HARNESS_URL,
1898
+ target: "_blank",
1899
+ rel: "noopener noreferrer",
1900
+ style: {
1901
+ ...S.btn,
1902
+ textDecoration: "none",
1903
+ fontWeight: 650,
1904
+ padding: "7px 12px",
1905
+ borderColor: "rgba(74,158,255,0.55)",
1906
+ background: "rgba(74,158,255,0.10)"
1907
+ },
1908
+ children: copy.openHarness
1909
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1910
+ style: {
1911
+ fontSize: 10.5,
1912
+ opacity: .52
1913
+ },
1914
+ children: copy.harnessHint
1915
+ })]
1916
+ })]
1344
1917
  }),
1345
1918
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1346
1919
  style: {
@@ -1381,7 +1954,7 @@ function WorkersPanel({ ctx }) {
1381
1954
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
1382
1955
  sectionId: "integrations",
1383
1956
  title: copy.sectionNames.integrations,
1384
- summary: sectionSummary(status?.claude?.installed ? "Claude " : "Claude ", status?.codex?.installed ? "Codex " : "Codex "),
1957
+ summary: sectionSummary(status?.claude?.ready ? "Claude READY" : "Claude CHECK", status?.codex?.ready ? "Codex READY" : "Codex CHECK"),
1385
1958
  expanded: !!expandedSections.integrations,
1386
1959
  onToggle: () => toggleSection("integrations"),
1387
1960
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1390,17 +1963,75 @@ function WorkersPanel({ ctx }) {
1390
1963
  flexDirection: "column",
1391
1964
  gap: 8
1392
1965
  },
1393
- children: [integrationRow("Claude Code", !!status?.claude?.installed, status?.claude?.installed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1394
- style: S.chip(!!status?.claude?.hud),
1395
- children: copy.hud
1396
- }) : null, "claude", "claude-uninstall", copy.tips.claude), integrationRow("Codex", !!status?.codex?.installed, null, "codex", "codex-uninstall", copy.tips.codex)]
1397
- })
1398
- }),
1399
- config && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1400
- (() => {
1401
- const clampNum = (v) => {
1402
- const n = Number(v);
1403
- if (Number.isNaN(n)) return 3;
1966
+ children: [
1967
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1968
+ style: S.block,
1969
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1970
+ style: S.settingTitle,
1971
+ children: copy.hostReadiness
1972
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1973
+ style: S.settingDesc,
1974
+ children: copy.hostReadinessHint
1975
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1976
+ style: {
1977
+ display: "grid",
1978
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1979
+ gap: "6px 10px"
1980
+ },
1981
+ children: hostReadiness.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1982
+ style: {
1983
+ display: "flex",
1984
+ gap: 8,
1985
+ alignItems: "center",
1986
+ minWidth: 0,
1987
+ padding: "4px 7px",
1988
+ border: "1px solid rgba(128,128,128,0.16)",
1989
+ borderRadius: 7
1990
+ },
1991
+ children: [
1992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1993
+ style: {
1994
+ flex: 1,
1995
+ minWidth: 0,
1996
+ fontSize: 11.5
1997
+ },
1998
+ children: copy.readinessLabels[row.id]
1999
+ }),
2000
+ row.detail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2001
+ style: {
2002
+ ...S.mono,
2003
+ opacity: .55
2004
+ },
2005
+ children: row.detail
2006
+ }),
2007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2008
+ style: readinessChip(row.state),
2009
+ children: copy.readinessStates[row.state]
2010
+ })
2011
+ ]
2012
+ }, row.id))
2013
+ })]
2014
+ }),
2015
+ integrationRow("Claude Code", !!status?.claude?.installed, !!status?.claude?.ready, status?.claude?.installed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2016
+ style: S.chip(!!status?.claude?.hud),
2017
+ children: copy.hud
2018
+ }) : null, "claude", "claude-uninstall", copy.tips.claude),
2019
+ integrationRow("Codex", !!status?.codex?.installed, !!status?.codex?.ready, status?.codex?.ready ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2020
+ title: (status?.codex?.missing ?? []).join(", "),
2021
+ style: {
2022
+ fontSize: 11,
2023
+ opacity: .6
2024
+ },
2025
+ children: (status?.codex?.missing ?? []).join(" · ")
2026
+ }), "codex", "codex-uninstall", copy.tips.codex)
2027
+ ]
2028
+ })
2029
+ }),
2030
+ config && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2031
+ (() => {
2032
+ const clampNum = (v) => {
2033
+ const n = Number(v);
2034
+ if (Number.isNaN(n)) return 3;
1404
2035
  return Math.max(1, Math.min(16, n));
1405
2036
  };
1406
2037
  const mode = config.collaboration_mode ?? "flash-only";
@@ -2188,12 +2819,90 @@ function WorkersPanel({ ctx }) {
2188
2819
  ] }))
2189
2820
  }),
2190
2821
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2822
+ sectionId: "adaptive",
2823
+ title: copy.sectionNames.adaptive,
2824
+ summary: sectionSummary(adaptive.enabled ? copy.adaptiveEnabled : "off", `${adaptive.min_samples}/${adaptive.window_size}`, copy.adaptiveBoundary),
2825
+ expanded: !!expandedSections.adaptive,
2826
+ onToggle: () => toggleSection("adaptive"),
2827
+ children: block({
2828
+ t: copy.adaptiveTitle,
2829
+ d: copy.adaptiveHint
2830
+ }, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2831
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2832
+ style: {
2833
+ ...S.field,
2834
+ flexDirection: "row",
2835
+ alignItems: "center",
2836
+ gap: 7,
2837
+ gridColumn: "1 / -1"
2838
+ },
2839
+ children: [
2840
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2841
+ type: "checkbox",
2842
+ checked: adaptive.enabled,
2843
+ onChange: (event) => saveAdaptive({
2844
+ ...adaptive,
2845
+ enabled: event.target.checked
2846
+ })
2847
+ }),
2848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2849
+ style: { fontSize: 12.5 },
2850
+ children: copy.adaptiveEnabled
2851
+ }),
2852
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2853
+ style: {
2854
+ fontSize: 11,
2855
+ opacity: .55
2856
+ },
2857
+ children: ["· ", copy.adaptiveBoundary]
2858
+ })
2859
+ ]
2860
+ }),
2861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2862
+ style: S.field,
2863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2864
+ style: S.fieldLabel,
2865
+ children: copy.adaptiveWindow
2866
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2867
+ type: "number",
2868
+ min: 1,
2869
+ max: 32,
2870
+ value: adaptive.window_size,
2871
+ style: S.input,
2872
+ onChange: (event) => setAdaptiveLocal({
2873
+ ...adaptive,
2874
+ window_size: clampInt(event.target.value, adaptive.window_size, 1, 32)
2875
+ }),
2876
+ onBlur: () => saveAdaptive(adaptive)
2877
+ })]
2878
+ }),
2879
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2880
+ style: S.field,
2881
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2882
+ style: S.fieldLabel,
2883
+ children: copy.adaptiveSamples
2884
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2885
+ type: "number",
2886
+ min: 1,
2887
+ max: adaptive.window_size,
2888
+ value: adaptive.min_samples,
2889
+ style: S.input,
2890
+ onChange: (event) => setAdaptiveLocal({
2891
+ ...adaptive,
2892
+ min_samples: clampInt(event.target.value, adaptive.min_samples, 1, adaptive.window_size)
2893
+ }),
2894
+ onBlur: () => saveAdaptive(adaptive)
2895
+ })]
2896
+ })
2897
+ ] }))
2898
+ }),
2899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
2191
2900
  sectionId: "runtime",
2192
2901
  title: copy.sectionNames.runtime,
2193
2902
  summary: sectionSummary(config.mode, `${config.default_timeout_seconds}s`, config.hub_url),
2194
2903
  expanded: !!expandedSections.runtime,
2195
2904
  onToggle: () => toggleSection("runtime"),
2196
- children: block(copy.cardRuntime, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2905
+ children: [block(copy.cardRuntime, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2197
2906
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2198
2907
  style: S.field,
2199
2908
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -2228,7 +2937,9 @@ function WorkersPanel({ ctx }) {
2228
2937
  max: 7200,
2229
2938
  value: config.default_timeout_seconds,
2230
2939
  onChange: (e) => fieldLocal("default_timeout_seconds", Number(e.target.value)),
2231
- onBlur: () => applyPatch({ default_timeout_seconds: config.default_timeout_seconds })
2940
+ onBlur: () => {
2941
+ applyPatch({ default_timeout_seconds: config.default_timeout_seconds });
2942
+ }
2232
2943
  })]
2233
2944
  }),
2234
2945
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -2244,13 +2955,18 @@ function WorkersPanel({ ctx }) {
2244
2955
  },
2245
2956
  value: config.hub_url,
2246
2957
  onChange: (e) => fieldLocal("hub_url", e.target.value),
2247
- onBlur: () => applyPatch({ hub_url: config.hub_url }),
2958
+ onBlur: () => {
2959
+ applyPatch({ hub_url: config.hub_url });
2960
+ },
2248
2961
  onKeyDown: (e) => {
2249
2962
  if (e.key === "Enter") e.currentTarget.blur();
2250
2963
  }
2251
2964
  })]
2252
2965
  })
2253
- ] }))
2966
+ ] })), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationSummary, {
2967
+ activation: config.config_activation,
2968
+ locale
2969
+ })]
2254
2970
  }),
2255
2971
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2256
2972
  sectionId: "multimodal",
@@ -2813,62 +3529,79 @@ function WorkersPanel({ ctx }) {
2813
3529
  },
2814
3530
  children: notice
2815
3531
  }),
2816
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
3532
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
2817
3533
  sectionId: "jobs",
2818
3534
  title: copy.sectionNames.jobs,
2819
3535
  summary: sectionSummary(copy.jobCount(jobs.length), jobs.some((job) => job.status === "running") && copy.runningCount(jobs.filter((job) => job.status === "running").length)),
2820
3536
  expanded: !!expandedSections.jobs,
2821
3537
  onToggle: () => toggleSection("jobs"),
2822
- children: jobs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3538
+ children: [jobs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2823
3539
  style: { opacity: .55 },
2824
3540
  children: copy.empty
2825
3541
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2826
- style: { overflowX: "auto" },
3542
+ style: {
3543
+ overflowX: "auto",
3544
+ border: "1px solid rgba(128,128,128,0.20)",
3545
+ borderRadius: 8,
3546
+ padding: "0 9px"
3547
+ },
2827
3548
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
2828
3549
  style: {
2829
3550
  borderCollapse: "collapse",
2830
3551
  width: "100%",
2831
3552
  tableLayout: "fixed",
2832
- minWidth: 760
3553
+ minWidth: 1020
2833
3554
  },
2834
3555
  children: [
2835
3556
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("colgroup", { children: [
2836
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 64 } }),
2837
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 58 } }),
3557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 68 } }),
2838
3558
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 72 } }),
2839
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 40 } }),
2840
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 100 } }),
3559
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 58 } }),
3560
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 190 } }),
3561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 70 } }),
3562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 70 } }),
3563
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 105 } }),
2841
3564
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 84 } }),
2842
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 340 } })
3565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 300 } })
2843
3566
  ] }),
2844
3567
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: [
2845
3568
  copy.col.id,
3569
+ copy.col.role,
2846
3570
  copy.col.source,
3571
+ copy.col.model,
2847
3572
  copy.col.tier,
2848
3573
  copy.col.status,
2849
3574
  copy.col.progress,
2850
3575
  copy.col.tokens,
2851
3576
  copy.col.task
2852
- ].map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
3577
+ ].map((heading) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
2853
3578
  style: {
2854
3579
  ...S.tight,
2855
- fontSize: 11,
3580
+ fontSize: 10.5,
2856
3581
  opacity: .55,
2857
3582
  fontWeight: 500
2858
3583
  },
2859
- children: h
2860
- }, h)) }) }),
3584
+ children: heading
3585
+ }, heading)) }) }),
2861
3586
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: jobs.map((job) => {
2862
3587
  const color = job.status === "running" ? "#4a9eff" : job.status === "done" ? "#3fb950" : "#f85149";
2863
3588
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
2864
3589
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2865
3590
  style: {
2866
3591
  ...S.tight,
2867
- ...S.mono
3592
+ ...S.mono,
3593
+ fontWeight: 600
2868
3594
  },
2869
3595
  title: job.id,
2870
3596
  children: String(job.id).replace(/-[a-z0-9]+$/, "")
2871
3597
  }),
3598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
3599
+ style: S.tight,
3600
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3601
+ style: S.chip(job.role === "reviewer"),
3602
+ children: job.role === "reviewer" ? "Reviewer" : "Worker"
3603
+ })
3604
+ }),
2872
3605
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2873
3606
  style: S.tight,
2874
3607
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -2876,6 +3609,18 @@ function WorkersPanel({ ctx }) {
2876
3609
  children: sourceLabel(job.source)
2877
3610
  })
2878
3611
  }),
3612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
3613
+ style: {
3614
+ ...S.tight,
3615
+ ...S.mono
3616
+ },
3617
+ title: `${job.provider ?? "—"} / ${job.model ?? "—"}`,
3618
+ children: [
3619
+ job.provider ?? "—",
3620
+ " / ",
3621
+ job.model ?? "—"
3622
+ ]
3623
+ }),
2879
3624
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
2880
3625
  style: S.tight,
2881
3626
  children: [
@@ -2884,19 +3629,24 @@ function WorkersPanel({ ctx }) {
2884
3629
  job.effort
2885
3630
  ]
2886
3631
  }),
2887
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
2888
- style: {
2889
- ...S.tight,
2890
- textAlign: "center"
2891
- },
3632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
3633
+ style: S.tight,
2892
3634
  title: `${job.status}${job.currentTool ? ` · ${job.currentTool}` : ""}`,
2893
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2894
- style: {
2895
- color,
2896
- cursor: "default"
2897
- },
2898
- children: "●"
2899
- })
3635
+ children: [
3636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3637
+ style: { color },
3638
+ "aria-hidden": "true",
3639
+ children: "●"
3640
+ }),
3641
+ " ",
3642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3643
+ style: {
3644
+ color,
3645
+ fontSize: 10.5
3646
+ },
3647
+ children: job.status
3648
+ })
3649
+ ]
2900
3650
  }),
2901
3651
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
2902
3652
  style: {
@@ -2906,11 +3656,11 @@ function WorkersPanel({ ctx }) {
2906
3656
  title: copy.progressTip(elapsed(job.startedAt, job.endedAt), job.turn, job.step, job.toolCalls ?? "-"),
2907
3657
  children: [
2908
3658
  elapsed(job.startedAt, job.endedAt),
2909
- " ",
3659
+ " · ",
2910
3660
  job.turn,
2911
3661
  ".",
2912
3662
  job.step,
2913
- " ",
3663
+ " · ",
2914
3664
  job.toolCalls ?? "-",
2915
3665
  "t"
2916
3666
  ]
@@ -2920,15 +3670,15 @@ function WorkersPanel({ ctx }) {
2920
3670
  ...S.tight,
2921
3671
  ...S.mono
2922
3672
  },
2923
- children: job.tokens ? `${ktok(job.tokens.input)}/${ktok(job.tokens.output)}` : "-"
3673
+ children: job.tokens ? `${ktok(job.tokens.input)}/${ktok(job.tokens.output)}` : ""
2924
3674
  }),
2925
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
3675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
2926
3676
  style: {
2927
3677
  ...S.cell,
2928
3678
  position: "relative"
2929
3679
  },
2930
- onMouseEnter: (e) => {
2931
- const rect = e.currentTarget.getBoundingClientRect();
3680
+ onMouseEnter: (event) => {
3681
+ const rect = event.currentTarget.getBoundingClientRect();
2932
3682
  setHoverTask({
2933
3683
  id: job.id,
2934
3684
  text: job.task,
@@ -2939,22 +3689,149 @@ function WorkersPanel({ ctx }) {
2939
3689
  });
2940
3690
  },
2941
3691
  onMouseLeave: () => setHoverTask(null),
2942
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3692
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2943
3693
  style: {
2944
3694
  whiteSpace: "nowrap",
2945
3695
  overflow: "hidden",
2946
3696
  textOverflow: "ellipsis",
2947
3697
  lineHeight: 1.45,
2948
- fontSize: 12.5
3698
+ fontSize: 12
2949
3699
  },
2950
3700
  children: job.task
2951
- })
3701
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3702
+ style: {
3703
+ fontSize: 10,
3704
+ opacity: .5
3705
+ },
3706
+ children: job.selection_source ?? "unknown"
3707
+ })]
2952
3708
  })
2953
3709
  ] }, job.id);
2954
3710
  }) })
2955
3711
  ]
2956
3712
  })
2957
- })
3713
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3714
+ style: {
3715
+ ...S.block,
3716
+ marginTop: 2
3717
+ },
3718
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3719
+ style: S.settingTitle,
3720
+ children: copy.modelActivity
3721
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3722
+ style: S.settingDesc,
3723
+ children: copy.modelActivityHint
3724
+ })] }), modelActivity.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3725
+ style: { opacity: .55 },
3726
+ children: copy.noModelActivity
3727
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3728
+ style: { overflowX: "auto" },
3729
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3730
+ role: "table",
3731
+ style: { minWidth: 620 },
3732
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3733
+ role: "row",
3734
+ style: {
3735
+ display: "grid",
3736
+ gridTemplateColumns: "minmax(190px, 1.35fr) 72px minmax(170px, 1fr) 125px",
3737
+ gap: 10,
3738
+ padding: "0 6px 5px",
3739
+ fontSize: 10.5,
3740
+ opacity: .55
3741
+ },
3742
+ children: [
3743
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3744
+ role: "columnheader",
3745
+ children: copy.model
3746
+ }),
3747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3748
+ role: "columnheader",
3749
+ children: copy.calls
3750
+ }),
3751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3752
+ role: "columnheader",
3753
+ children: [
3754
+ copy.invocationSource,
3755
+ " / ",
3756
+ copy.routingSource
3757
+ ]
3758
+ }),
3759
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3760
+ role: "columnheader",
3761
+ children: copy.lastCalled
3762
+ })
3763
+ ]
3764
+ }), modelActivity.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3765
+ role: "row",
3766
+ style: {
3767
+ display: "grid",
3768
+ gridTemplateColumns: "minmax(190px, 1.35fr) 72px minmax(170px, 1fr) 125px",
3769
+ gap: 10,
3770
+ alignItems: "center",
3771
+ padding: "7px 6px",
3772
+ borderTop: "1px solid rgba(128,128,128,0.16)"
3773
+ },
3774
+ children: [
3775
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3776
+ role: "cell",
3777
+ style: { minWidth: 0 },
3778
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
3779
+ style: {
3780
+ display: "block",
3781
+ fontSize: 12
3782
+ },
3783
+ children: entry.model
3784
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3785
+ style: {
3786
+ ...S.mono,
3787
+ opacity: .55
3788
+ },
3789
+ children: entry.provider
3790
+ })]
3791
+ }),
3792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3793
+ role: "cell",
3794
+ style: {
3795
+ ...S.mono,
3796
+ fontSize: 15,
3797
+ fontWeight: 650
3798
+ },
3799
+ children: entry.count
3800
+ }),
3801
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3802
+ role: "cell",
3803
+ style: {
3804
+ minWidth: 0,
3805
+ fontSize: 11.5
3806
+ },
3807
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3808
+ style: { display: "block" },
3809
+ children: entry.task_sources.map(sourceLabel).join(" · ")
3810
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3811
+ style: {
3812
+ display: "block",
3813
+ opacity: .55
3814
+ },
3815
+ children: [
3816
+ entry.selection_sources.join(" · "),
3817
+ " · ",
3818
+ entry.roles.join(" / ")
3819
+ ]
3820
+ })]
3821
+ }),
3822
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3823
+ role: "cell",
3824
+ style: {
3825
+ ...S.mono,
3826
+ fontSize: 10.5
3827
+ },
3828
+ children: formatTimestamp(entry.last_called_at, locale)
3829
+ })
3830
+ ]
3831
+ }, `${entry.provider}/${entry.model}`))]
3832
+ })
3833
+ })]
3834
+ })]
2958
3835
  }),
2959
3836
  hoverTask && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2960
3837
  style: {
@@ -2983,6 +3860,7 @@ function WorkersPanel({ ctx }) {
2983
3860
  }
2984
3861
  function apply$1(ctx) {
2985
3862
  let runningCount = 0;
3863
+ let officialSurface = null;
2986
3864
  ctx.slots.inject("settings.section", () => {
2987
3865
  let dispose = register();
2988
3866
  function register() {
@@ -3011,7 +3889,12 @@ function apply$1(ctx) {
3011
3889
  const poll = async () => {
3012
3890
  if (document.visibilityState !== "visible") return;
3013
3891
  try {
3014
- const r = await (await fetch(`${API$1}/jobs`, { cache: "no-store" })).json();
3892
+ if (officialSurface === null) {
3893
+ const response = await fetch(`${API}/bridge-status`, { cache: "no-store" });
3894
+ officialSurface = classifyCrewSurface({ bridgeStatus: response.ok ? await response.json() : null }) === CREW_UI_SURFACES.OFFICIAL;
3895
+ }
3896
+ if (!officialSurface) return;
3897
+ const r = await (await fetch(`${API}/jobs`, { cache: "no-store" })).json();
3015
3898
  const n = r.ok ? (r.jobs ?? []).filter((j) => j.status === "running").length : 0;
3016
3899
  if (n !== runningCount) {
3017
3900
  runningCount = n;
@@ -3031,413 +3914,10 @@ function apply$1(ctx) {
3031
3914
  });
3032
3915
  }
3033
3916
  //#endregion
3034
- //#region src/client/activation-summary.tsx
3035
- const ORDER = [
3036
- "live",
3037
- "next-workflow",
3038
- "next-session",
3039
- "restart-required"
3040
- ];
3041
- const LABELS = {
3042
- zh: {
3043
- title: "配置生效边界",
3044
- hint: "这里显示全局 Settings 保存后的实际生效时机;会话内 dsh_worker_config 覆盖可能更早生效。",
3045
- boundary: {
3046
- live: "Live · 当前运行时",
3047
- "next-workflow": "Next workflow · 下一个任务",
3048
- "next-session": "Next session · 新 CC / Codex 会话",
3049
- "restart-required": "Restart required · 重启 DSH / MCP"
3050
- }
3051
- },
3052
- en: {
3053
- title: "Configuration activation boundaries",
3054
- hint: "Shows when persisted Settings changes actually take effect. Session-level dsh_worker_config overrides may activate earlier.",
3055
- boundary: {
3056
- live: "Live · current runtime",
3057
- "next-workflow": "Next workflow",
3058
- "next-session": "Next session · new CC / Codex session",
3059
- "restart-required": "Restart required · restart DSH / MCP"
3060
- }
3061
- }
3062
- };
3063
- function groupActivationBoundaries(activation = {}) {
3064
- const grouped = Object.fromEntries(ORDER.map((boundary) => [boundary, []]));
3065
- for (const [key, entry] of Object.entries(activation)) if (entry?.global && grouped[entry.global]) grouped[entry.global].push(key);
3066
- for (const boundary of ORDER) grouped[boundary].sort();
3067
- return grouped;
3068
- }
3069
- function ActivationSummary({ activation, locale }) {
3070
- if (!activation || Object.keys(activation).length === 0) return null;
3071
- const copy = LABELS[locale === "zh" ? "zh" : "en"];
3072
- const grouped = groupActivationBoundaries(activation);
3073
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3074
- style: {
3075
- border: "1px solid rgba(128,128,128,0.22)",
3076
- borderRadius: 8,
3077
- padding: "9px 12px",
3078
- display: "flex",
3079
- flexDirection: "column",
3080
- gap: 5
3081
- },
3082
- children: [
3083
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3084
- style: {
3085
- fontWeight: 600,
3086
- fontSize: 12.5
3087
- },
3088
- children: copy.title
3089
- }),
3090
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3091
- style: {
3092
- fontSize: 11,
3093
- opacity: .6
3094
- },
3095
- children: copy.hint
3096
- }),
3097
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3098
- style: {
3099
- display: "grid",
3100
- gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
3101
- gap: "5px 12px"
3102
- },
3103
- children: ORDER.map((boundary) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3104
- style: { minWidth: 0 },
3105
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3106
- style: {
3107
- fontSize: 10.5,
3108
- opacity: .7,
3109
- fontWeight: 600
3110
- },
3111
- children: copy.boundary[boundary]
3112
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3113
- style: {
3114
- fontSize: 10.5,
3115
- opacity: .55,
3116
- wordBreak: "break-word"
3117
- },
3118
- children: grouped[boundary].length ? grouped[boundary].join(" · ") : "—"
3119
- })]
3120
- }, boundary))
3121
- })
3122
- ]
3123
- });
3124
- }
3125
- //#endregion
3126
3917
  //#region src/client/entry.tsx
3127
3918
  const inject = inject$1;
3128
- const API = "/_dsh/dsh-crew";
3129
- const DEFAULT_ADAPTIVE = {
3130
- enabled: false,
3131
- window_size: 8,
3132
- min_samples: 2
3133
- };
3134
- function clampInt(value, fallback, min, max) {
3135
- const parsed = Number(value);
3136
- if (!Number.isInteger(parsed)) return fallback;
3137
- return Math.max(min, Math.min(max, parsed));
3138
- }
3139
- function normalizeAdaptive(value) {
3140
- const windowSize = clampInt(value?.window_size, DEFAULT_ADAPTIVE.window_size, 1, 32);
3141
- return {
3142
- enabled: value?.enabled === true,
3143
- window_size: windowSize,
3144
- min_samples: clampInt(value?.min_samples, DEFAULT_ADAPTIVE.min_samples, 1, windowSize)
3145
- };
3146
- }
3147
- function useLocale(ctx) {
3148
- return (0, react.useSyncExternalStore)((notify) => ctx.on("locale/change", notify), () => ctx.locale.getLocale().active, () => ctx.locale.getLocale().active);
3149
- }
3150
- function AdaptiveRoutingPanel({ ctx }) {
3151
- const zh = useLocale(ctx) === "zh";
3152
- const [adaptive, setAdaptive] = (0, react.useState)(DEFAULT_ADAPTIVE);
3153
- const [loaded, setLoaded] = (0, react.useState)(false);
3154
- const [saving, setSaving] = (0, react.useState)(false);
3155
- const [message, setMessage] = (0, react.useState)("");
3156
- const copy = zh ? {
3157
- title: "自适应模型路由(实验)",
3158
- hint: "默认关闭。仅对系统自动产生的候选做健康排序;显式 Provider / Model 优先级永远保持原序。信号只来自本进程内 Crew 已观察到的成功、失败、超时与粗粒度延迟,不读取额度、价格或凭据。重启 Hub 会清空健康历史。",
3159
- enabled: "启用自适应路由",
3160
- window: "健康窗口",
3161
- minSamples: "最少样本",
3162
- windowHint: "每个 role/provider/model 最多参考最近 1–32 次结果。",
3163
- minHint: "达到该样本数后才允许健康分数影响自动候选顺序。",
3164
- boundary: "生效边界:下一工作流",
3165
- saved: "已保存",
3166
- loading: "加载中…"
3167
- } : {
3168
- title: "Adaptive Model Routing (experimental)",
3169
- hint: "Off by default. Health ordering applies only to automatically derived candidates; explicit Provider / Model priorities always keep their order. Signals are limited to Crew-observed success, failure, timeout, and coarse latency in this process—never quota, pricing, or credentials. Restarting the Hub clears the history.",
3170
- enabled: "Enable adaptive routing",
3171
- window: "Health window",
3172
- minSamples: "Minimum samples",
3173
- windowHint: "Use at most the most recent 1–32 outcomes per role/provider/model.",
3174
- minHint: "Health may affect automatic candidate order only after this many samples.",
3175
- boundary: "Activation boundary: next workflow",
3176
- saved: "Saved",
3177
- loading: "Loading…"
3178
- };
3179
- (0, react.useEffect)(() => {
3180
- let cancelled = false;
3181
- const load = async () => {
3182
- try {
3183
- const res = await fetch(`${API}/config?lang=${zh ? "zh" : "en"}`, { cache: "no-store" });
3184
- const body = await res.json();
3185
- if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
3186
- if (!cancelled) {
3187
- setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
3188
- setMessage("");
3189
- setLoaded(true);
3190
- }
3191
- } catch (err) {
3192
- if (!cancelled) {
3193
- setMessage(err?.message ?? String(err));
3194
- setLoaded(true);
3195
- }
3196
- }
3197
- };
3198
- load();
3199
- return () => {
3200
- cancelled = true;
3201
- };
3202
- }, [zh]);
3203
- const save = async (candidate) => {
3204
- const next = normalizeAdaptive(candidate);
3205
- setAdaptive(next);
3206
- setSaving(true);
3207
- try {
3208
- const res = await fetch(`${API}/config?lang=${zh ? "zh" : "en"}`, {
3209
- method: "POST",
3210
- headers: { "content-type": "application/json" },
3211
- body: JSON.stringify({ worker: { model_policy: { adaptive: next } } })
3212
- });
3213
- const body = await res.json();
3214
- if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
3215
- setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
3216
- setMessage(copy.saved);
3217
- setTimeout(() => setMessage(""), 1500);
3218
- } catch (err) {
3219
- setMessage(err?.message ?? String(err));
3220
- } finally {
3221
- setSaving(false);
3222
- }
3223
- };
3224
- if (!loaded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3225
- style: {
3226
- fontSize: 12,
3227
- opacity: .6
3228
- },
3229
- children: copy.loading
3230
- });
3231
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3232
- style: {
3233
- display: "flex",
3234
- flexDirection: "column",
3235
- gap: 10,
3236
- fontSize: 13,
3237
- lineHeight: 1.55
3238
- },
3239
- children: [
3240
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3241
- style: {
3242
- fontWeight: 600,
3243
- fontSize: 14
3244
- },
3245
- children: copy.title
3246
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3247
- style: {
3248
- opacity: .68,
3249
- fontSize: 12,
3250
- marginTop: 2
3251
- },
3252
- children: copy.hint
3253
- })] }),
3254
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3255
- style: {
3256
- display: "flex",
3257
- alignItems: "center",
3258
- gap: 7
3259
- },
3260
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3261
- type: "checkbox",
3262
- checked: adaptive.enabled,
3263
- disabled: saving,
3264
- onChange: (event) => {
3265
- save({
3266
- ...adaptive,
3267
- enabled: event.target.checked
3268
- });
3269
- }
3270
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.enabled })]
3271
- }),
3272
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3273
- style: {
3274
- display: "grid",
3275
- gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
3276
- gap: 10
3277
- },
3278
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3279
- style: {
3280
- display: "flex",
3281
- flexDirection: "column",
3282
- gap: 4
3283
- },
3284
- children: [
3285
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3286
- style: {
3287
- fontSize: 12,
3288
- fontWeight: 500
3289
- },
3290
- children: copy.window
3291
- }),
3292
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3293
- type: "number",
3294
- min: 1,
3295
- max: 32,
3296
- value: adaptive.window_size,
3297
- disabled: saving,
3298
- onChange: (event) => {
3299
- const windowSize = clampInt(event.target.value, adaptive.window_size, 1, 32);
3300
- setAdaptive((current) => ({
3301
- ...current,
3302
- window_size: windowSize,
3303
- min_samples: Math.min(current.min_samples, windowSize)
3304
- }));
3305
- },
3306
- onBlur: () => {
3307
- save(adaptive);
3308
- },
3309
- style: {
3310
- padding: "5px 7px",
3311
- borderRadius: 5,
3312
- border: "1px solid rgba(128,128,128,0.35)",
3313
- background: "transparent",
3314
- color: "inherit"
3315
- }
3316
- }),
3317
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3318
- style: {
3319
- fontSize: 10.5,
3320
- opacity: .55
3321
- },
3322
- children: copy.windowHint
3323
- })
3324
- ]
3325
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3326
- style: {
3327
- display: "flex",
3328
- flexDirection: "column",
3329
- gap: 4
3330
- },
3331
- children: [
3332
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3333
- style: {
3334
- fontSize: 12,
3335
- fontWeight: 500
3336
- },
3337
- children: copy.minSamples
3338
- }),
3339
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3340
- type: "number",
3341
- min: 1,
3342
- max: adaptive.window_size,
3343
- value: adaptive.min_samples,
3344
- disabled: saving,
3345
- onChange: (event) => setAdaptive((current) => ({
3346
- ...current,
3347
- min_samples: clampInt(event.target.value, current.min_samples, 1, current.window_size)
3348
- })),
3349
- onBlur: () => {
3350
- save(adaptive);
3351
- },
3352
- style: {
3353
- padding: "5px 7px",
3354
- borderRadius: 5,
3355
- border: "1px solid rgba(128,128,128,0.35)",
3356
- background: "transparent",
3357
- color: "inherit"
3358
- }
3359
- }),
3360
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3361
- style: {
3362
- fontSize: 10.5,
3363
- opacity: .55
3364
- },
3365
- children: copy.minHint
3366
- })
3367
- ]
3368
- })]
3369
- }),
3370
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3371
- style: {
3372
- display: "flex",
3373
- gap: 8,
3374
- alignItems: "center",
3375
- fontSize: 11.5,
3376
- opacity: .62
3377
- },
3378
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.boundary }), message && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", message] })]
3379
- })
3380
- ]
3381
- });
3382
- }
3383
- function ActivationBoundaryPanel({ ctx }) {
3384
- const locale = useLocale(ctx);
3385
- const [activation, setActivation] = (0, react.useState)(null);
3386
- const [error, setError] = (0, react.useState)("");
3387
- (0, react.useEffect)(() => {
3388
- let cancelled = false;
3389
- const load = async () => {
3390
- try {
3391
- const res = await fetch(`${API}/config?lang=${locale === "zh" ? "zh" : "en"}`, { cache: "no-store" });
3392
- const body = await res.json();
3393
- if (!cancelled) {
3394
- if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
3395
- setActivation(body?.config?.config_activation ?? null);
3396
- setError("");
3397
- }
3398
- } catch (err) {
3399
- if (!cancelled) setError(err?.message ?? String(err));
3400
- }
3401
- };
3402
- load();
3403
- return () => {
3404
- cancelled = true;
3405
- };
3406
- }, [locale]);
3407
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3408
- style: {
3409
- display: "flex",
3410
- flexDirection: "column",
3411
- gap: 8,
3412
- fontSize: 13,
3413
- lineHeight: 1.55
3414
- },
3415
- children: [error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3416
- style: {
3417
- fontSize: 11.5,
3418
- opacity: .6
3419
- },
3420
- children: error
3421
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationSummary, {
3422
- activation: activation ?? void 0,
3423
- locale
3424
- })]
3425
- });
3426
- }
3427
3919
  function apply(ctx) {
3428
3920
  apply$1(ctx);
3429
- ctx.slots.inject("settings.section", () => ctx.slots.register({
3430
- name: "settings.section",
3431
- id: "dsh-crew-adaptive-routing",
3432
- order: 65,
3433
- label: () => ctx.locale.getLocale().active === "zh" ? "DSH Crew · 自适应路由" : "DSH Crew · Adaptive Routing"
3434
- }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdaptiveRoutingPanel, { ctx })));
3435
- ctx.slots.inject("settings.section", () => ctx.slots.register({
3436
- name: "settings.section",
3437
- id: "dsh-crew-runtime-controls",
3438
- order: 66,
3439
- label: () => ctx.locale.getLocale().active === "zh" ? "DSH Crew · 生效边界" : "DSH Crew · Activation"
3440
- }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationBoundaryPanel, { ctx })));
3441
3921
  }
3442
3922
  //#endregion
3443
3923
  exports.apply = apply;