@ran-sh/dsh-crew 1.1.1 → 1.2.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.claude-plugin/marketplace.json +16 -17
  2. package/.claude-plugin/plugin.json +7 -1
  3. package/README.md +44 -15
  4. package/README.zh.md +37 -12
  5. package/agents/ds-flash.md +16 -18
  6. package/agents/ds-pro.md +16 -24
  7. package/agents/ds-reviewer.md +15 -15
  8. package/agents/ds-worker.md +16 -16
  9. package/bin/dsh-crew.mjs +16 -12
  10. package/codex/AGENTS.md +37 -85
  11. package/codex/agents/ds-flash.toml +18 -14
  12. package/codex/agents/ds-pro.toml +18 -15
  13. package/codex/agents/ds-reviewer.toml +17 -13
  14. package/codex/agents/ds-worker.toml +17 -13
  15. package/codex/prompts/dsh-config.md +17 -1
  16. package/codex/prompts/dsh-status.md +5 -1
  17. package/commands/config.md +16 -4
  18. package/commands/status.md +6 -2
  19. package/docs/installation.md +54 -9
  20. package/docs/readiness-matrix.md +14 -6
  21. package/docs/ui-surfaces.md +32 -17
  22. package/lib/client.js +769 -177
  23. package/official-web-bridge/lib/client.js +297 -209
  24. package/official-web-bridge/overlay-entry.mjs +59 -0
  25. package/package.json +6 -3
  26. package/scripts/build-client.mjs +2 -0
  27. package/scripts/setup.mjs +8 -7
  28. package/scripts/verify-crew-ui-polish.mjs +145 -0
  29. package/scripts/verify-history-ui.mjs +97 -0
  30. package/scripts/verify-npm-install.mjs +1 -1
  31. package/src/client/history-panel.tsx +108 -0
  32. package/src/client/host-readiness.mjs +15 -6
  33. package/src/client/index.tsx +91 -69
  34. package/src/client/model-callability-view.mjs +17 -0
  35. package/src/client/panel-chrome.tsx +40 -0
  36. package/src/client/quick-panel.tsx +88 -73
  37. package/src/client/readiness-envelope.mjs +34 -0
  38. package/src/client/task-telemetry.mjs +11 -4
  39. package/src/config-readiness.mjs +181 -115
  40. package/src/extension-contract.mjs +44 -26
  41. package/src/history/admission-gate.mjs +51 -0
  42. package/src/history/archive-store.mjs +261 -0
  43. package/src/history/cleanup-plan.mjs +64 -0
  44. package/src/history/http.mjs +32 -0
  45. package/src/history/operation.mjs +86 -0
  46. package/src/history/runner.mjs +51 -0
  47. package/src/history/runtime.mjs +28 -0
  48. package/src/history/service.mjs +137 -0
  49. package/src/history/state.mjs +27 -0
  50. package/src/hub/index.mjs +141 -24
  51. package/src/hub-client.mjs +17 -15
  52. package/src/image-output.mjs +71 -0
  53. package/src/install/install-legacy.mjs +126 -23
  54. package/src/install/npx-lifecycle.mjs +373 -64
  55. package/src/install/official-frontend-assets.mjs +78 -0
  56. package/src/install/payload-content.mjs +88 -0
  57. package/src/install/windows-startup.mjs +107 -19
  58. package/src/install/windows-supervisor-adapter.mjs +443 -0
  59. package/src/install/windows-supervisor-lifecycle.mjs +782 -0
  60. package/src/install/zcode.mjs +53 -14
  61. package/src/job-contracts.mjs +37 -31
  62. package/src/jobs.mjs +2 -2
  63. package/src/mcp-runtime.mjs +74 -9
  64. package/src/model-callability-contract.mjs +79 -0
  65. package/src/multimodal.mjs +0 -0
  66. package/src/official-web-bridge.mjs +0 -2
  67. package/src/provider-health.mjs +130 -124
  68. package/src/readiness-matrix.mjs +20 -15
  69. package/src/runtime-identity-contract.mjs +34 -0
  70. package/src/runtime-identity.mjs +14 -4
  71. package/src/runtime-readiness-snapshot.mjs +285 -87
  72. package/src/server.mjs +86 -76
  73. package/src/standalone-sdk.mjs +23 -0
  74. package/src/supervisor/restart-request.mjs +64 -10
  75. package/src/workflow-runtime.mjs +28 -13
  76. package/windows/start-dsh-crew.cmd +57 -57
  77. package/windows/start-dsh-crew.ps1 +327 -51
  78. package/windows/supervisor-control.ps1 +467 -0
  79. package/zcode/AGENTS.md +4 -1
  80. package/zcode/agents/ds-reviewer.md +31 -36
  81. package/zcode/agents/ds-worker.md +31 -39
  82. package/zcode/commands/dsh-config.md +17 -6
  83. package/zcode/commands/dsh-status.md +5 -4
  84. package/.mcp.json +0 -8
package/lib/client.js CHANGED
@@ -163,14 +163,28 @@ function ActivationSummary({ activation, locale }) {
163
163
  ]
164
164
  });
165
165
  }
166
+ function nonEmpty(value) {
167
+ return typeof value === "string" && value.trim().length > 0;
168
+ }
169
+ function isCompleteRuntimeIdentity(identity) {
170
+ return Boolean(identity && typeof identity === "object" && nonEmpty(identity.execution_plane) && nonEmpty(identity.profile) && Number.isInteger(identity.listen_port) && identity.listen_port > 0 && nonEmpty(identity.runtime_id));
171
+ }
172
+ function sameCompleteRuntimeIdentity(left, right) {
173
+ return isCompleteRuntimeIdentity(left) && isCompleteRuntimeIdentity(right) && left.execution_plane === right.execution_plane && left.profile === right.profile && left.listen_port === right.listen_port && left.runtime_id === right.runtime_id;
174
+ }
175
+ function isExactNativeCrewIdentity(identity) {
176
+ return isCompleteRuntimeIdentity(identity) && identity.execution_plane === "hub-3210" && identity.profile === "dsh-crew" && identity.listen_port === 3210;
177
+ }
166
178
  //#endregion
167
179
  //#region src/client/host-readiness.mjs
168
180
  const READINESS_STATES = Object.freeze({
169
181
  READY: "READY",
170
182
  DEGRADED: "DEGRADED",
171
183
  UNAVAILABLE: "UNAVAILABLE",
172
- UNKNOWN: "UNKNOWN"
184
+ UNKNOWN: "UNKNOWN",
185
+ NOT_APPLICABLE: "NOT_APPLICABLE"
173
186
  });
187
+ const MAX_RUNTIME_SNAPSHOT_AGE_MS = 3e5;
174
188
  function componentState(status, keys, { aligned = false } = {}) {
175
189
  if (status === void 0 || status === null || typeof status !== "object") return READINESS_STATES.UNKNOWN;
176
190
  if (status.installed === false) return READINESS_STATES.UNAVAILABLE;
@@ -182,7 +196,11 @@ function componentState(status, keys, { aligned = false } = {}) {
182
196
  function runtimeState(runtime, readinessSnapshot) {
183
197
  if (readinessSnapshot && typeof readinessSnapshot === "object") {
184
198
  const identity = readinessSnapshot.runtime;
185
- if (!(identity?.execution_plane === "hub-3210" && identity.profile === "dsh-crew" && Number(identity.listen_port) === 3210 && typeof identity.runtime_id === "string" && identity.runtime_id.trim())) return READINESS_STATES.UNAVAILABLE;
199
+ if (!isExactNativeCrewIdentity(identity)) return READINESS_STATES.UNAVAILABLE;
200
+ if (runtime && (!isCompleteRuntimeIdentity(runtime) || !sameCompleteRuntimeIdentity(runtime, identity))) return READINESS_STATES.UNKNOWN;
201
+ if (!Number.isFinite(readinessSnapshot.captured_at)) return READINESS_STATES.UNKNOWN;
202
+ if (readinessSnapshot.captured_at > Date.now() + 3e4) return READINESS_STATES.UNKNOWN;
203
+ if (Date.now() - readinessSnapshot.captured_at > MAX_RUNTIME_SNAPSHOT_AGE_MS) return READINESS_STATES.DEGRADED;
186
204
  const row = Array.isArray(readinessSnapshot.readiness_matrix?.rows) ? readinessSnapshot.readiness_matrix.rows.find((entry) => entry?.id === "hub_compatibility") : void 0;
187
205
  if (row?.status === "PASS") return READINESS_STATES.READY;
188
206
  if (row?.status === "FAIL") return READINESS_STATES.UNAVAILABLE;
@@ -191,11 +209,11 @@ function runtimeState(runtime, readinessSnapshot) {
191
209
  }
192
210
  if (runtime === void 0) return READINESS_STATES.UNKNOWN;
193
211
  if (runtime === null) return READINESS_STATES.UNAVAILABLE;
194
- return runtime?.ok === true && runtime.service === "dsh-crew-hub" ? READINESS_STATES.READY : READINESS_STATES.DEGRADED;
212
+ return runtime?.ok === true && isExactNativeCrewIdentity(runtime) ? READINESS_STATES.READY : READINESS_STATES.UNKNOWN;
195
213
  }
196
214
  function bridgeState(surface) {
197
215
  if (surface === "official-bridge") return READINESS_STATES.READY;
198
- if (surface === "native-crew-harness") return READINESS_STATES.UNKNOWN;
216
+ if (surface === "native-crew-harness") return READINESS_STATES.NOT_APPLICABLE;
199
217
  return READINESS_STATES.UNKNOWN;
200
218
  }
201
219
  /** Project only structured, non-secret installer/runtime evidence. */
@@ -248,6 +266,135 @@ function projectHostReadiness({ installStatus, runtime, surface, readinessSnapsh
248
266
  }
249
267
  ];
250
268
  }
269
+ const ROLES = ["worker", "reviewer"];
270
+ const STATES = /* @__PURE__ */ new Set([
271
+ "CALLABLE",
272
+ "NOT_CALLABLE",
273
+ "STALE",
274
+ "UNKNOWN",
275
+ "NOT_APPLICABLE"
276
+ ]);
277
+ const SOURCES = /* @__PURE__ */ new Set(["provider_health", "execution"]);
278
+ const MAX_EVIDENCE_TTL_MS = 864e5;
279
+ function exactKeys(value, keys) {
280
+ return value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
281
+ }
282
+ function route(value) {
283
+ return typeof value?.provider === "string" && value.provider.trim() && typeof value?.model === "string" && value.model.trim() ? {
284
+ provider: value.provider.trim(),
285
+ model: value.model.trim()
286
+ } : null;
287
+ }
288
+ function validEvidence(role, now) {
289
+ if (!SOURCES.has(role.source) || !Number.isFinite(role.observed_at) || role.observed_at > now || !Number.isFinite(role.expires_at) || role.expires_at <= now || role.expires_at - role.observed_at > MAX_EVIDENCE_TTL_MS) return false;
290
+ if (role.source === "execution") {
291
+ if (!role.last_success || typeof role.last_success !== "object" || typeof role.last_success.job_id !== "string" || !role.last_success.job_id.trim() || role.last_success.observed_at !== role.observed_at || role.last_success.expires_at !== role.expires_at) return false;
292
+ }
293
+ return true;
294
+ }
295
+ function validateModelCallabilityV2({ projection, runtime, expectedEnabledRoles, expectedSelections, now = Date.now() } = {}) {
296
+ const fail = (reason_code) => ({
297
+ ok: false,
298
+ reason_code,
299
+ state: "UNKNOWN"
300
+ });
301
+ if (!projection || typeof projection !== "object" || projection.schema_version !== 2) return fail("MODEL_CALLABILITY_SCHEMA_INVALID");
302
+ if (!isExactNativeCrewIdentity(runtime) || !isExactNativeCrewIdentity(projection.runtime_identity) || !sameCompleteRuntimeIdentity(runtime, projection.runtime_identity) || projection.current_runtime_id !== runtime.runtime_id) return fail("MODEL_CALLABILITY_RUNTIME_MISMATCH");
303
+ if (!Number.isFinite(projection.captured_at) || projection.captured_at > now) return fail("MODEL_CALLABILITY_CAPTURE_INVALID");
304
+ if (!exactKeys(projection.enabled_roles, ROLES) || ROLES.some((role) => typeof projection.enabled_roles[role] !== "boolean")) return fail("MODEL_CALLABILITY_ROLES_INVALID");
305
+ if (expectedEnabledRoles && (!exactKeys(expectedEnabledRoles, ROLES) || ROLES.some((role) => projection.enabled_roles[role] !== expectedEnabledRoles[role]))) return fail("MODEL_CALLABILITY_ROLE_CONFIG_MISMATCH");
306
+ if (expectedSelections && !exactKeys(expectedSelections, ROLES)) return fail("MODEL_CALLABILITY_EXPECTED_SELECTIONS_INVALID");
307
+ if (!exactKeys(projection.roles, ROLES)) return fail("MODEL_CALLABILITY_ROLE_EVIDENCE_MISSING");
308
+ for (const roleName of ROLES) {
309
+ const role = projection.roles[roleName];
310
+ if (!role || !STATES.has(role.state)) return fail("MODEL_CALLABILITY_ROLE_STATE_INVALID");
311
+ const selected = route(role.selected);
312
+ if (role.selected !== void 0 && role.selected !== null && !selected) return fail("MODEL_CALLABILITY_SELECTION_INVALID");
313
+ if (expectedSelections) {
314
+ const expectedRaw = expectedSelections[roleName];
315
+ const expected = expectedRaw === null ? null : route(expectedRaw);
316
+ if (expectedRaw !== null && !expected) return fail("MODEL_CALLABILITY_EXPECTED_SELECTION_INVALID");
317
+ if (expected === null !== (selected === null) || expected && (expected.provider !== selected.provider || expected.model !== selected.model)) return fail("MODEL_CALLABILITY_SELECTION_MISMATCH");
318
+ }
319
+ if (projection.enabled_roles[roleName] === false) {
320
+ if (role.state !== "NOT_APPLICABLE" || role.reason_code !== "ROLE_DISABLED") return fail("MODEL_CALLABILITY_DISABLED_ROLE_INVALID");
321
+ continue;
322
+ }
323
+ if (["CALLABLE", "NOT_CALLABLE"].includes(role.state) && !selected) return fail("MODEL_CALLABILITY_SELECTION_INVALID");
324
+ if (projection.enabled_roles[roleName] === true && role.state === "NOT_APPLICABLE") return fail("MODEL_CALLABILITY_ENABLED_ROLE_NOT_APPLICABLE");
325
+ if (["CALLABLE", "NOT_CALLABLE"].includes(role.state)) {
326
+ if (!validEvidence(role, now)) return fail("MODEL_CALLABILITY_EVIDENCE_INVALID");
327
+ }
328
+ }
329
+ const activeRoles = ROLES.map((role) => projection.roles[role]).filter((_, index) => projection.enabled_roles[ROLES[index]] === true);
330
+ const activeStates = activeRoles.map((role) => role.state);
331
+ const state = activeStates.includes("NOT_CALLABLE") ? "NOT_CALLABLE" : activeStates.length > 0 && activeStates.every((value) => value === "CALLABLE") ? "CALLABLE" : activeStates.includes("STALE") ? "STALE" : "UNKNOWN";
332
+ if (projection.overall !== state) return fail("MODEL_CALLABILITY_OVERALL_CONTRADICTION");
333
+ const evidenceExpirations = activeRoles.map((role) => role.expires_at).filter((value) => Number.isFinite(value));
334
+ const expectedExpires = evidenceExpirations.length ? Math.min(...evidenceExpirations) : null;
335
+ if (projection.expires_at !== expectedExpires) return fail("MODEL_CALLABILITY_EXPIRY_CONTRADICTION");
336
+ if (state === "CALLABLE" && (!Number.isFinite(projection.expires_at) || projection.expires_at <= now)) return fail("MODEL_CALLABILITY_EXPIRED");
337
+ return {
338
+ ok: true,
339
+ state,
340
+ reason_code: "MODEL_CALLABILITY_VALID"
341
+ };
342
+ }
343
+ //#endregion
344
+ //#region src/client/model-callability-view.mjs
345
+ function modelCallabilityState(projection, runtime = null, now = Date.now()) {
346
+ if (!projection || typeof projection !== "object" || typeof projection.overall !== "string") return READINESS_STATES.UNKNOWN;
347
+ const validation = validateModelCallabilityV2({
348
+ projection,
349
+ runtime,
350
+ now
351
+ });
352
+ if (!validation.ok) return READINESS_STATES.UNKNOWN;
353
+ if (validation.state === "CALLABLE") return READINESS_STATES.READY;
354
+ if (validation.state === "NOT_CALLABLE") return READINESS_STATES.UNAVAILABLE;
355
+ if (validation.state === "STALE") return READINESS_STATES.DEGRADED;
356
+ return READINESS_STATES.UNKNOWN;
357
+ }
358
+ //#endregion
359
+ //#region src/client/readiness-envelope.mjs
360
+ function readinessExpiryDelay(expiresAt, now = Date.now()) {
361
+ return Number.isFinite(expiresAt) ? Math.max(0, expiresAt - now + 10) : null;
362
+ }
363
+ /**
364
+ * Apply one extension response to the atomic runtime/readiness envelope.
365
+ * Older responses are ignored so a restart cannot be rolled back by a slow
366
+ * request that started before the new runtime identity was observed.
367
+ */
368
+ function acceptReadinessResponse(response, { generation, latestGeneration } = {}) {
369
+ if (generation !== latestGeneration) return {
370
+ accepted: false,
371
+ envelope: null
372
+ };
373
+ const extensionRuntime = response?.extension?.runtime;
374
+ const snapshot = response?.extension?.readiness_snapshot;
375
+ const valid = response?.ok === true && isExactNativeCrewIdentity(extensionRuntime) && isExactNativeCrewIdentity(snapshot?.runtime) && sameCompleteRuntimeIdentity(extensionRuntime, snapshot.runtime);
376
+ const expectedEnabledRoles = {
377
+ worker: response?.extension?.capabilities?.["deepseek.worker"] === true,
378
+ reviewer: response?.extension?.capabilities?.["deepseek.reviewer"] === true
379
+ };
380
+ const projection = valid ? validateModelCallabilityV2({
381
+ projection: snapshot.model_callability,
382
+ runtime: extensionRuntime,
383
+ expectedEnabledRoles,
384
+ expectedSelections: {
385
+ worker: snapshot.worker?.selected ?? null,
386
+ reviewer: snapshot.reviewer?.selected ?? null
387
+ }
388
+ }) : { ok: false };
389
+ return {
390
+ accepted: true,
391
+ envelope: valid && projection.ok ? {
392
+ runtime: extensionRuntime,
393
+ snapshot,
394
+ expiresAt: snapshot.model_callability.expires_at
395
+ } : {}
396
+ };
397
+ }
251
398
  //#endregion
252
399
  //#region src/client/surface-detection.mjs
253
400
  const CREW_UI_SURFACES = Object.freeze({
@@ -308,9 +455,11 @@ function hasInvocationEvidence(job) {
308
455
  return Number(job?.turn ?? 0) > 0 || Number(job?.toolCalls ?? 0) > 0 || tokenCount > 0;
309
456
  }
310
457
  /**
311
- * Build a bounded, presentation-only model activity summary from the jobs the
312
- * Hub already exposes. No prompts, results, credentials, or new persistence
313
- * are introduced here.
458
+ * Build a bounded, presentation-only, job-level model activity summary from
459
+ * the jobs the Hub already exposes. Each qualifying job contributes one row
460
+ * count for its selected provider/model; the count is not an inferred number
461
+ * of underlying model turns. No prompts, results, credentials, or new
462
+ * persistence are introduced here.
314
463
  */
315
464
  function aggregateModelInvocations(jobs = []) {
316
465
  const groups = /* @__PURE__ */ new Map();
@@ -332,7 +481,7 @@ function aggregateModelInvocations(jobs = []) {
332
481
  current.count += 1;
333
482
  current.taskSources.add(clean(job?.source, "api"));
334
483
  current.selectionSources.add(clean(job?.selection_source, "unknown"));
335
- current.roles.add(job?.role === "reviewer" ? "reviewer" : "worker");
484
+ if (job?.role === "worker" || job?.role === "reviewer") current.roles.add(job.role);
336
485
  const calledAt = validTimestamp(job?.startedAt);
337
486
  if (calledAt && (!current.lastCalledAt || Date.parse(calledAt) > Date.parse(current.lastCalledAt))) current.lastCalledAt = calledAt;
338
487
  groups.set(key, current);
@@ -350,10 +499,474 @@ function aggregateModelInvocations(jobs = []) {
350
499
  }));
351
500
  }
352
501
  //#endregion
502
+ //#region src/client/panel-chrome.tsx
503
+ function PanelStyles() {
504
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
505
+ .dsh-crew-ui { --crew-line: rgba(128,128,128,.24); --crew-soft: rgba(128,128,128,.045); --crew-accent: #2563eb; font-size: 13px; line-height: 1.55; min-width: 0; }
506
+ .dsh-crew-ui *, .dsh-crew-ui *::before, .dsh-crew-ui *::after { box-sizing: border-box; }
507
+ .dsh-crew-ui .crew-panel-header { border: 1px solid var(--crew-line); border-radius: 12px; padding: 16px; background: var(--crew-soft); display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px 16px; }
508
+ .dsh-crew-ui .crew-panel-heading { flex: 1 1 260px; min-width: 0; }
509
+ .dsh-crew-ui .crew-eyebrow { font-size: 10.5px; letter-spacing: .055em; opacity: .65; margin-bottom: 4px; }
510
+ .dsh-crew-ui .crew-panel-title { margin: 0; font-size: 19px; font-weight: 680; line-height: 1.3; letter-spacing: -.025em; }
511
+ .dsh-crew-ui .crew-panel-description { margin: 5px 0 0; font-size: 12px; opacity: .72; overflow-wrap: anywhere; }
512
+ .dsh-crew-ui .crew-panel-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
513
+ .dsh-crew-ui .crew-nav-link { display: inline-flex; align-items: center; justify-content: center; color: var(--crew-accent); background: transparent; border: 1px solid var(--crew-line); border-radius: 8px; padding: 7px 10px; font-size: 12px; font-weight: 600; text-decoration: none; flex-shrink: 0; }
514
+ .dsh-crew-ui button:focus-visible, .dsh-crew-ui a:focus-visible, .dsh-crew-ui input:focus-visible, .dsh-crew-ui summary:focus-visible { outline: 2px solid var(--crew-accent); outline-offset: 3px; }
515
+ .dsh-crew-ui button:disabled { cursor: not-allowed; opacity: .4; }
516
+ .dsh-crew-ui .crew-nav-link:hover, .dsh-crew-ui .crew-section-trigger:hover, .dsh-crew-ui summary:hover { background: rgba(128,128,128,.09); }
517
+ .dsh-crew-ui .crew-section-heading { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
518
+ .dsh-crew-ui .crew-section-title { font-weight: 650; font-size: 13px; }
519
+ .dsh-crew-ui .crew-section-summary { font-size: 11.5px; opacity: .65; overflow-wrap: anywhere; }
520
+ .dsh-crew-ui .crew-config-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin: 6px 0 2px; }
521
+ @media (prefers-color-scheme: dark) { .dsh-crew-ui { --crew-accent: #8ab4ff; } }
522
+ @media (prefers-reduced-motion: reduce) { .dsh-crew-ui * { transition: none !important; } }
523
+ ` });
524
+ }
525
+ function PanelHeader({ title, eyebrow, description, href, linkText, children }) {
526
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
527
+ className: "crew-panel-header",
528
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
529
+ className: "crew-panel-heading",
530
+ children: [
531
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
532
+ className: "crew-eyebrow",
533
+ children: eyebrow
534
+ }),
535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
536
+ className: "crew-panel-title",
537
+ children: title
538
+ }),
539
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
540
+ className: "crew-panel-description",
541
+ children: description
542
+ }),
543
+ children && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
544
+ className: "crew-panel-badges",
545
+ children
546
+ })
547
+ ]
548
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
549
+ className: "crew-nav-link",
550
+ href,
551
+ target: "_blank",
552
+ rel: "noopener noreferrer",
553
+ children: linkText
554
+ })]
555
+ });
556
+ }
557
+ //#endregion
558
+ //#region src/client/history-panel.tsx
559
+ const API$1 = "/_dsh/dsh-crew/history/";
560
+ const pending = (phase) => !!phase && ![
561
+ "IDLE",
562
+ "DONE",
563
+ "FAILED",
564
+ "ROLLED_BACK"
565
+ ].includes(phase);
566
+ const copy = {
567
+ zh: {
568
+ title: "工作区与会话清理",
569
+ intro: "仅影响 3210 的工作区记录与会话日志;不删除项目文件、附件或 3080 数据。",
570
+ operation: "操作",
571
+ archive: "归档(可恢复)",
572
+ delete: "删除(不可恢复)",
573
+ scope: "时间范围",
574
+ all: "全部",
575
+ before: "指定时间之前",
576
+ time: "创建时间早于",
577
+ timeHint: "按本机时区选择,严格按创建时间筛选;包含新会话的工作区会保留。",
578
+ preview: "预览清理范围",
579
+ confirm: "确认执行",
580
+ acknowledgement: "删除确认:请输入 DELETE",
581
+ idle: "会短暂停止并重启 3210;请先结束或关闭所有正在使用的 3210 会话。",
582
+ consent: "我已确认范围,并同意短暂重启 3210",
583
+ restore: "恢复",
584
+ archives: "已归档批次",
585
+ empty: "暂无可恢复的归档",
586
+ working: "维护中,正在等待 3210 重新连接…",
587
+ refresh: "刷新页面与工作区列表",
588
+ recover: "恢复未完成的维护",
589
+ recovery: "维护尚未完成,请使用恢复按钮;若 3210 无法连接,在终端运行 dsh-crew history recover。",
590
+ cancelQueued: "取消尚未开始的维护",
591
+ cancelQueuedHint: "仅取消尚未开始的维护,不清理任何数据;如果操作已开始,请等待完成。",
592
+ count: (w, s) => `${w} 个工作区 · ${s} 个会话`,
593
+ confirmRestore: "恢复这一批归档?将短暂重启 3210,不会覆盖冲突数据。",
594
+ phase: {
595
+ IDLE: "就绪",
596
+ QUEUED: "已提交",
597
+ STOPPING: "停止后台",
598
+ APPLYING: "处理数据",
599
+ STARTING: "启动后台",
600
+ VERIFYING: "验证结果",
601
+ DONE: "已完成",
602
+ FAILED: "未执行成功",
603
+ ROLLED_BACK: "已回滚",
604
+ RECOVERY_REQUIRED: "需要恢复"
605
+ }
606
+ },
607
+ en: {
608
+ title: "Workspace & session cleanup",
609
+ intro: "Only 3210 workspace records and session logs. Project files, attachments and 3080 data are untouched.",
610
+ operation: "Action",
611
+ archive: "Archive (restorable)",
612
+ delete: "Delete (permanent)",
613
+ scope: "Time range",
614
+ all: "All",
615
+ before: "Before a date",
616
+ time: "Created before",
617
+ timeHint: "Local timezone; strict creation-time cutoff. Workspaces with newer sessions are kept.",
618
+ preview: "Preview cleanup",
619
+ confirm: "Confirm operation",
620
+ acknowledgement: "Type DELETE to confirm deletion",
621
+ idle: "3210 will briefly stop and restart. End or close all active 3210 conversations first.",
622
+ consent: "I reviewed the scope and agree to restart 3210",
623
+ restore: "Restore",
624
+ archives: "Archived batches",
625
+ empty: "No restorable archives",
626
+ working: "Maintenance in progress; waiting for 3210 to reconnect…",
627
+ refresh: "Reload page and workspace list",
628
+ recover: "Recover unfinished maintenance",
629
+ recovery: "Maintenance is unfinished. Use recovery; if 3210 is unreachable, run dsh-crew history recover in a terminal.",
630
+ cancelQueued: "Cancel unstarted maintenance",
631
+ cancelQueuedHint: "Cancel only if maintenance has not started. No data is cleaned; if already running, wait for completion.",
632
+ count: (w, s) => `${w} workspaces · ${s} sessions`,
633
+ confirmRestore: "Restore this archive? 3210 will briefly restart; conflicting data will not be overwritten.",
634
+ phase: {
635
+ IDLE: "Ready",
636
+ QUEUED: "Queued",
637
+ STOPPING: "Stopping backend",
638
+ APPLYING: "Processing data",
639
+ STARTING: "Starting backend",
640
+ VERIFYING: "Verifying",
641
+ DONE: "Completed",
642
+ FAILED: "Failed",
643
+ ROLLED_BACK: "Rolled back",
644
+ RECOVERY_REQUIRED: "Recovery required"
645
+ }
646
+ }
647
+ };
648
+ function HistoryPanel({ locale }) {
649
+ const t = copy[locale === "zh" ? "zh" : "en"];
650
+ const [open, setOpen] = (0, react.useState)(false);
651
+ const [operation, setOperation] = (0, react.useState)("archive");
652
+ const [scope, setScope] = (0, react.useState)("all");
653
+ const [before, setBefore] = (0, react.useState)("");
654
+ const [preview, setPreview] = (0, react.useState)(null);
655
+ const [status, setStatus] = (0, react.useState)({ phase: "IDLE" });
656
+ const [archives, setArchives] = (0, react.useState)([]);
657
+ const [busy, setBusy] = (0, react.useState)(false);
658
+ const [consent, setConsent] = (0, react.useState)(false);
659
+ const [ack, setAck] = (0, react.useState)("");
660
+ const [notice, setNotice] = (0, react.useState)("");
661
+ const [connected, setConnected] = (0, react.useState)(false);
662
+ const locked = busy || pending(status.phase);
663
+ const invalidate = () => {
664
+ setPreview(null);
665
+ setConsent(false);
666
+ setAck("");
667
+ };
668
+ async function request(action, body) {
669
+ const res = await fetch(API$1 + action, {
670
+ cache: "no-store",
671
+ ...body === void 0 ? {} : {
672
+ method: "POST",
673
+ headers: { "content-type": "application/json" },
674
+ body: JSON.stringify(body)
675
+ }
676
+ });
677
+ const data = await res.json();
678
+ if (!res.ok || !data.ok) throw Error(data.code ?? `HTTP ${res.status}`);
679
+ return data;
680
+ }
681
+ (0, react.useEffect)(() => {
682
+ if (!open) return;
683
+ let disposed = false;
684
+ const poll = async () => {
685
+ try {
686
+ const [s, a] = await Promise.all([request("status"), request("archives")]);
687
+ if (!disposed) {
688
+ setStatus(s.status);
689
+ setArchives(a.archives);
690
+ setConnected(true);
691
+ }
692
+ } catch {
693
+ if (!disposed) setConnected(false);
694
+ }
695
+ };
696
+ poll();
697
+ const timer = setInterval(poll, 2500);
698
+ return () => {
699
+ disposed = true;
700
+ clearInterval(timer);
701
+ };
702
+ }, [open]);
703
+ async function act(action, body) {
704
+ setBusy(true);
705
+ setNotice("");
706
+ try {
707
+ const result = await request(action, body);
708
+ if (result.preview) {
709
+ setPreview(result.preview);
710
+ setConsent(false);
711
+ setAck("");
712
+ }
713
+ if (result.status) {
714
+ setStatus(result.status);
715
+ setPreview(null);
716
+ }
717
+ } catch (error) {
718
+ setNotice(String(error.message));
719
+ setPreview(null);
720
+ } finally {
721
+ setBusy(false);
722
+ }
723
+ }
724
+ const style = {
725
+ border: "1px solid rgba(128,128,128,.25)",
726
+ borderRadius: 8,
727
+ padding: "7px 10px",
728
+ color: "inherit",
729
+ background: "transparent",
730
+ font: "inherit"
731
+ };
732
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
733
+ style: {
734
+ border: "1px solid rgba(128,128,128,.24)",
735
+ borderRadius: 10,
736
+ padding: "10px 13px"
737
+ },
738
+ onToggle: (e) => setOpen(e.currentTarget.open),
739
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
740
+ style: {
741
+ cursor: "pointer",
742
+ fontWeight: 650
743
+ },
744
+ children: t.title
745
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
746
+ style: {
747
+ display: "flex",
748
+ flexDirection: "column",
749
+ gap: 10,
750
+ marginTop: 12
751
+ },
752
+ children: [
753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
754
+ style: {
755
+ margin: 0,
756
+ opacity: .75
757
+ },
758
+ children: t.intro
759
+ }),
760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
761
+ style: {
762
+ display: "flex",
763
+ gap: 12,
764
+ flexWrap: "wrap"
765
+ },
766
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
767
+ t.operation,
768
+ " ",
769
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
770
+ style,
771
+ value: operation,
772
+ disabled: locked,
773
+ onChange: (e) => {
774
+ setOperation(e.target.value);
775
+ invalidate();
776
+ },
777
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
778
+ value: "archive",
779
+ children: t.archive
780
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
781
+ value: "delete",
782
+ children: t.delete
783
+ })]
784
+ })
785
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
786
+ t.scope,
787
+ " ",
788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
789
+ style,
790
+ value: scope,
791
+ disabled: locked,
792
+ onChange: (e) => {
793
+ setScope(e.target.value);
794
+ invalidate();
795
+ },
796
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
797
+ value: "all",
798
+ children: t.all
799
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
800
+ value: "before",
801
+ children: t.before
802
+ })]
803
+ })
804
+ ] })]
805
+ }),
806
+ scope === "before" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
807
+ t.time,
808
+ " ",
809
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
810
+ type: "datetime-local",
811
+ style,
812
+ value: before,
813
+ disabled: locked,
814
+ onChange: (e) => {
815
+ setBefore(e.target.value);
816
+ invalidate();
817
+ }
818
+ })
819
+ ] }),
820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
821
+ style: {
822
+ fontSize: 11.5,
823
+ opacity: .7
824
+ },
825
+ children: t.timeHint
826
+ }),
827
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
828
+ type: "button",
829
+ style,
830
+ disabled: locked || !connected || scope === "before" && !Number.isFinite(new Date(before).getTime()),
831
+ onClick: () => void act("preview", {
832
+ operation,
833
+ scope,
834
+ ...scope === "before" ? { before: new Date(before).toISOString() } : {}
835
+ }),
836
+ children: t.preview
837
+ }),
838
+ preview && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
839
+ style: {
840
+ ...style,
841
+ display: "flex",
842
+ flexDirection: "column",
843
+ gap: 9
844
+ },
845
+ children: [
846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.count(preview.counts.workspaces, preview.counts.sessions) }),
847
+ preview.before && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
848
+ t.time,
849
+ ": ",
850
+ new Date(preview.before).toLocaleString()
851
+ ] }),
852
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [
853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t.preview }),
854
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: preview.items?.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
855
+ item.title,
856
+ " ",
857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: item.id })
858
+ ] }, item.id)) }),
859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
860
+ style: {
861
+ overflowWrap: "anywhere",
862
+ fontSize: 11
863
+ },
864
+ children: [preview.sessionIds?.slice(0, 100).join(", "), preview.sessionIds?.length > 100 ? " …" : ""]
865
+ })
866
+ ] }),
867
+ !preview.executable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
868
+ role: "alert",
869
+ children: preview.blockedReason
870
+ }),
871
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t.idle }),
872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
874
+ type: "checkbox",
875
+ checked: consent,
876
+ disabled: locked,
877
+ onChange: (e) => setConsent(e.target.checked)
878
+ }),
879
+ " ",
880
+ t.consent
881
+ ] }),
882
+ operation === "delete" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
883
+ t.acknowledgement,
884
+ " ",
885
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
886
+ style,
887
+ value: ack,
888
+ disabled: locked,
889
+ onChange: (e) => setAck(e.target.value)
890
+ })
891
+ ] }),
892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
893
+ type: "button",
894
+ style,
895
+ disabled: locked || !consent || !preview.executable || operation === "delete" && ack !== "DELETE",
896
+ onClick: () => void act("execute", {
897
+ planId: preview.planId,
898
+ confirm: true,
899
+ acknowledgement: ack
900
+ }),
901
+ children: t.confirm
902
+ })
903
+ ]
904
+ }),
905
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
906
+ role: "status",
907
+ "aria-live": "polite",
908
+ children: [
909
+ t.phase[status.phase] ?? status.phase,
910
+ status.code ? ` · ${status.code}` : "",
911
+ notice ? ` · ${notice}` : ""
912
+ ]
913
+ }),
914
+ !connected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
915
+ role: "status",
916
+ children: [pending(status.phase) ? t.working : "3210 history API unavailable", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t.recovery })]
917
+ }),
918
+ ["RECOVERY_REQUIRED", "QUEUED"].includes(status.phase) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: status.phase === "QUEUED" ? t.cancelQueuedHint : t.recovery }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
919
+ type: "button",
920
+ style,
921
+ disabled: busy,
922
+ onClick: () => {
923
+ if (window.confirm(status.phase === "QUEUED" ? t.cancelQueuedHint : t.recovery)) act("recover", { confirm: true });
924
+ },
925
+ children: status.phase === "QUEUED" ? t.cancelQueued : t.recover
926
+ })] }),
927
+ ["DONE", "ROLLED_BACK"].includes(status.phase) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
928
+ type: "button",
929
+ style,
930
+ onClick: () => window.location.reload(),
931
+ children: t.refresh
932
+ }),
933
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.archives }),
934
+ archives.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
935
+ style: { opacity: .6 },
936
+ children: t.empty
937
+ }),
938
+ archives.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
939
+ style: {
940
+ ...style,
941
+ display: "flex",
942
+ gap: 8,
943
+ flexWrap: "wrap",
944
+ alignItems: "center"
945
+ },
946
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
947
+ style: { flex: 1 },
948
+ children: a.invalid ? `${a.id} · ${a.code}` : `${new Date(a.createdAt).toLocaleString()} · ${t.count(a.workspaces, a.sessions)}`
949
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
950
+ type: "button",
951
+ style,
952
+ disabled: locked || a.invalid,
953
+ onClick: () => {
954
+ if (window.confirm(t.confirmRestore)) act("restore", {
955
+ archiveId: a.id,
956
+ confirm: true
957
+ });
958
+ },
959
+ children: t.restore
960
+ })]
961
+ }, a.id))
962
+ ]
963
+ })]
964
+ });
965
+ }
966
+ //#endregion
353
967
  //#region src/client/index.tsx
354
968
  const inject$1 = ["slots", "locale"];
355
969
  const API = "/_dsh/dsh-crew";
356
- const CREW_HARNESS_URL = "http://127.0.0.1:3210/";
357
970
  const CREW_CONTROL_PLANE_URL = "http://127.0.0.1:3080/";
358
971
  const DEFAULT_ADAPTIVE = {
359
972
  enabled: false,
@@ -377,11 +990,17 @@ const COPY = {
377
990
  zh: {
378
991
  label: "DSH Crew",
379
992
  title: "DSH Crew",
380
- intro: "日常 Crew 控制台:在一个入口里配置编排、角色、模型路由与宿主集成,并跟踪真实 Worker / Reviewer 任务。",
993
+ openOfficial: "打开 3080 官方界面 →",
994
+ intro: "管理工作流、模型路由与宿主集成。展开分区查看和修改设置。",
995
+ surfaceLabel: "3210 / 高级配置",
381
996
  integrations: "集成",
382
997
  installed: "已安装",
383
998
  notInstalled: "未安装",
384
- ready: "可调用",
999
+ ready: "集成就绪",
1000
+ integrationReady: "集成就绪",
1001
+ modelCallable: "模型可调用",
1002
+ modelNotCallable: "模型未验证",
1003
+ modelUnknown: "模型未知",
385
1004
  notReady: "未就绪",
386
1005
  hud: "HUD 段",
387
1006
  install: "安装",
@@ -408,8 +1027,7 @@ const COPY = {
408
1027
  providers: "多模态适配器",
409
1028
  jobs: "任务状态"
410
1029
  },
411
- openHarness: "打开 3210 Crew Harness →",
412
- harnessHint: "底层 Provider、Harness Models 与运行时配置",
1030
+ harnessHint: "DeepSeek 官方 Harness 的 dsh-crew profile:Provider、Harness Models 与运行时配置",
413
1031
  hostReadiness: "宿主集成就绪度",
414
1032
  hostReadinessHint: "只使用结构化安装与运行时证据;缺少证据不会显示 READY。",
415
1033
  readinessLabels: {
@@ -418,14 +1036,15 @@ const COPY = {
418
1036
  ds_reviewer: "ds-reviewer",
419
1037
  claude_plugin: "Claude plugin",
420
1038
  zcode_mcp: "ZCode MCP",
421
- crew_harness: "Crew Harness",
1039
+ crew_harness: "Crew plugin profile",
422
1040
  official_bridge: "Official bridge"
423
1041
  },
424
1042
  readinessStates: {
425
1043
  READY: "READY",
426
1044
  DEGRADED: "DEGRADED",
427
1045
  UNAVAILABLE: "UNAVAILABLE",
428
- UNKNOWN: "UNKNOWN"
1046
+ UNKNOWN: "UNKNOWN",
1047
+ NOT_APPLICABLE: "N/A"
429
1048
  },
430
1049
  globalHint: "修改即时保存到 ~/.config/dsh-crew/config.json;CC / Codex 的新会话自动读取为默认值(会话内可用 /dsh-crew:config 临时覆盖)。",
431
1050
  orchestration: "Agent 编排",
@@ -524,7 +1143,7 @@ const COPY = {
524
1143
  visionModel: "视觉模型",
525
1144
  imagegenProvider: "生图 provider",
526
1145
  harnessProviders: "Harness Providers",
527
- harnessProviderHint: "3210 Crew Harness 的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。",
1146
+ harnessProviderHint: "DeepSeek 官方 Harness 的 3210 dsh-crew profile 中的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。",
528
1147
  providerState: (state) => `状态:${state}`,
529
1148
  providerModels: (count) => `${count} 个模型`,
530
1149
  providerJobs: (count) => `${count} 个运行任务`,
@@ -672,10 +1291,10 @@ const COPY = {
672
1291
  adaptiveWindow: "健康窗口",
673
1292
  adaptiveSamples: "最少样本",
674
1293
  adaptiveBoundary: "下一个工作流生效",
675
- modelActivity: "模型调用概览",
676
- modelActivityHint: "聚合本 Hub 内存中最近 500 个任务的真实调用证据,最多显示 50 个模型;不保存提示词、结果或凭据。",
677
- noModelActivity: "还没有真实模型调用。",
678
- calls: "调用次数",
1294
+ modelActivity: "模型活动概览",
1295
+ modelActivityHint: "聚合本 Hub 内存中最近 500 个含真实调用证据的任务;每个模型按任务计数(不是推测底层轮次),最多显示 50 个模型。不保存提示词、结果或凭据。",
1296
+ noModelActivity: "还没有真实模型活动。",
1297
+ calls: "任务数",
679
1298
  invocationSource: "任务来源",
680
1299
  routingSource: "路由来源",
681
1300
  lastCalled: "最近调用",
@@ -715,11 +1334,17 @@ const COPY = {
715
1334
  en: {
716
1335
  label: "DSH Crew",
717
1336
  title: "DSH Crew",
718
- intro: "The daily Crew console: configure orchestration, roles, model routing, and host integrations in one place while tracking real Worker / Reviewer jobs.",
1337
+ intro: "Manage workflows, model routing and host integrations. Expand a section to edit its settings.",
1338
+ surfaceLabel: "3210 / ADVANCED SETTINGS",
1339
+ openOfficial: "Open official frontend (3080) →",
719
1340
  integrations: "Integrations",
720
1341
  installed: "installed",
721
1342
  notInstalled: "not installed",
722
- ready: "ready",
1343
+ ready: "integration ready",
1344
+ integrationReady: "integration ready",
1345
+ modelCallable: "model callable",
1346
+ modelNotCallable: "model not verified",
1347
+ modelUnknown: "model unknown",
723
1348
  notReady: "not ready",
724
1349
  hud: "HUD segment",
725
1350
  install: "Install",
@@ -746,8 +1371,7 @@ const COPY = {
746
1371
  providers: "Multimodal adapters",
747
1372
  jobs: "Task status"
748
1373
  },
749
- openHarness: "Open 3210 Crew Harness →",
750
- harnessHint: "Low-level providers, Harness Models, and runtime configuration",
1374
+ harnessHint: "The official DeepSeek Harness dsh-crew profile: providers, Harness Models, and runtime configuration",
751
1375
  hostReadiness: "Host integration readiness",
752
1376
  hostReadinessHint: "Uses structured installer and runtime evidence only; missing evidence is never READY.",
753
1377
  readinessLabels: {
@@ -756,14 +1380,15 @@ const COPY = {
756
1380
  ds_reviewer: "ds-reviewer",
757
1381
  claude_plugin: "Claude plugin",
758
1382
  zcode_mcp: "ZCode MCP",
759
- crew_harness: "Crew Harness",
1383
+ crew_harness: "Crew plugin profile",
760
1384
  official_bridge: "Official bridge"
761
1385
  },
762
1386
  readinessStates: {
763
1387
  READY: "READY",
764
1388
  DEGRADED: "DEGRADED",
765
1389
  UNAVAILABLE: "UNAVAILABLE",
766
- UNKNOWN: "UNKNOWN"
1390
+ UNKNOWN: "UNKNOWN",
1391
+ NOT_APPLICABLE: "N/A"
767
1392
  },
768
1393
  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).",
769
1394
  orchestration: "Agent orchestration",
@@ -862,7 +1487,7 @@ const COPY = {
862
1487
  visionModel: "Vision model",
863
1488
  imagegenProvider: "Image-gen provider",
864
1489
  harnessProviders: "Harness Providers",
865
- harnessProviderHint: "Live Provider registration and lifecycle in the 3210 Crew Harness; separate from vision / image-gen adapters.",
1490
+ harnessProviderHint: "Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210; separate from vision / image-gen adapters.",
866
1491
  providerState: (state) => `State: ${state}`,
867
1492
  providerModels: (count) => `${count} models`,
868
1493
  providerJobs: (count) => `${count} running jobs`,
@@ -1010,10 +1635,10 @@ const COPY = {
1010
1635
  adaptiveWindow: "Health window",
1011
1636
  adaptiveSamples: "Minimum samples",
1012
1637
  adaptiveBoundary: "Effective for the next workflow",
1013
- modelActivity: "Model invocation overview",
1014
- 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.",
1015
- noModelActivity: "No real model invocations yet.",
1016
- calls: "Calls",
1638
+ modelActivity: "Model activity overview",
1639
+ modelActivityHint: "Aggregates the latest 500 in-memory Hub jobs with real model-activity evidence; each model is counted once per qualifying job (not an inferred turn count). Shows at most 50 models; prompts, results, and credentials are never stored here.",
1640
+ noModelActivity: "No real model activity yet.",
1641
+ calls: "Jobs",
1017
1642
  invocationSource: "Task source",
1018
1643
  routingSource: "Routing source",
1019
1644
  lastCalled: "Last called",
@@ -1323,43 +1948,30 @@ function CollapsibleSection({ sectionId, title, summary, expanded, onToggle, chi
1323
1948
  "data-section-id": sectionId,
1324
1949
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1325
1950
  type: "button",
1951
+ className: "crew-section-trigger",
1326
1952
  style: S.sectionButton,
1327
1953
  "aria-expanded": expanded,
1328
1954
  "aria-controls": bodyId,
1329
1955
  onClick: onToggle,
1330
- children: [
1331
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1332
- "aria-hidden": "true",
1333
- style: {
1334
- width: 14,
1335
- opacity: .62,
1336
- transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
1337
- transition: "transform 140ms ease"
1338
- },
1339
- children: "›"
1340
- }),
1341
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1342
- style: {
1343
- fontWeight: 650,
1344
- fontSize: 13,
1345
- minWidth: 92
1346
- },
1956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1957
+ "aria-hidden": "true",
1958
+ style: {
1959
+ width: 14,
1960
+ opacity: .62,
1961
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
1962
+ transition: "transform 140ms ease"
1963
+ },
1964
+ children: "›"
1965
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1966
+ className: "crew-section-heading",
1967
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1968
+ className: "crew-section-title",
1347
1969
  children: title
1348
- }),
1349
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1350
- title: summary,
1351
- style: {
1352
- flex: 1,
1353
- minWidth: 0,
1354
- fontSize: 11.5,
1355
- opacity: .58,
1356
- overflow: "hidden",
1357
- textOverflow: "ellipsis",
1358
- whiteSpace: "nowrap"
1359
- },
1970
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1971
+ className: "crew-section-summary",
1360
1972
  children: summary
1361
- })
1362
- ]
1973
+ })]
1974
+ })]
1363
1975
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1364
1976
  id: bodyId,
1365
1977
  hidden: !expanded,
@@ -1392,7 +2004,7 @@ function formatTimestamp(value, locale) {
1392
2004
  }).format(new Date(value));
1393
2005
  }
1394
2006
  function readinessChip(state) {
1395
- const color = state === READINESS_STATES.READY ? "#3fb950" : state === READINESS_STATES.DEGRADED ? "#c98735" : state === READINESS_STATES.UNAVAILABLE ? "#f85149" : "inherit";
2007
+ const color = state === READINESS_STATES.READY ? "#3fb950" : state === READINESS_STATES.DEGRADED ? "#c98735" : state === READINESS_STATES.UNAVAILABLE ? "#f85149" : state === READINESS_STATES.NOT_APPLICABLE ? "#7d8590" : "inherit";
1396
2008
  return {
1397
2009
  fontSize: 10.5,
1398
2010
  fontWeight: 650,
@@ -1407,7 +2019,7 @@ function MinimalCrewPanel({ locale, surface, runtime }) {
1407
2019
  const zh = locale === "zh";
1408
2020
  const native = surface === CREW_UI_SURFACES.NATIVE;
1409
2021
  const title = native ? zh ? "DSH Crew Runtime" : "DSH Crew Runtime" : zh ? "DSH Crew Surface 未确认" : "DSH Crew surface not verified";
1410
- 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.";
2022
+ const hint = native ? zh ? "这里是 DeepSeek 官方 Harness 的专用 dsh-crew profile,DSH Crew 作为插件加载其中。请使用 Harness 原生菜单管理 Provider、Harness Models、Agent 预设与底层配置。" : "This is the dedicated dsh-crew profile of the official DeepSeek Harness, with DSH Crew loaded as a plugin. Use the native Harness menus for Providers, Harness Models, Agent presets, and low-level settings." : zh ? "无法用结构化后端证据确认当前界面,因此已保守隐藏 Crew 编排、任务与宿主集成功能。" : "Structured backend evidence could not identify this surface, so Crew orchestration, jobs, and host integrations are conservatively hidden.";
1411
2023
  const runtimeReady = runtime?.ok === true && runtime?.service === "dsh-crew-hub";
1412
2024
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1413
2025
  style: {
@@ -1448,7 +2060,7 @@ function MinimalCrewPanel({ locale, surface, runtime }) {
1448
2060
  children: runtimeReady ? `Runtime ${runtime.runtime_version ?? "READY"}` : "Runtime UNKNOWN"
1449
2061
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1450
2062
  style: readinessChip(native ? READINESS_STATES.READY : READINESS_STATES.UNKNOWN),
1451
- children: native ? "3210 · isolated" : "surface · unknown"
2063
+ children: native ? "3210 · official profile + Crew plugin" : "surface · unknown"
1452
2064
  })]
1453
2065
  }),
1454
2066
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
@@ -1464,7 +2076,7 @@ function MinimalCrewPanel({ locale, surface, runtime }) {
1464
2076
  borderColor: "rgba(74,158,255,0.55)",
1465
2077
  background: "rgba(74,158,255,0.08)"
1466
2078
  },
1467
- children: zh ? "打开 3080 DSH Crew 控制台 →" : "Open the 3080 DSH Crew console →"
2079
+ children: zh ? "打开官方 Harness 界面(3080) →" : "Open official Harness (3080) →"
1468
2080
  })
1469
2081
  ]
1470
2082
  })
@@ -1498,8 +2110,17 @@ function WorkersPanel({ ctx }) {
1498
2110
  const [testResult, setTestResult] = (0, react.useState)(null);
1499
2111
  const [expandedSections, setExpandedSections] = (0, react.useState)(() => readSectionState(sectionStorage()));
1500
2112
  const [surface, setSurface] = (0, react.useState)("detecting");
1501
- const [runtimeInfo, setRuntimeInfo] = (0, react.useState)(void 0);
1502
- const [readinessSnapshot, setReadinessSnapshot] = (0, react.useState)(void 0);
2113
+ const [readinessEnvelope, setReadinessEnvelope] = (0, react.useState)({});
2114
+ const readinessGeneration = (0, react.useRef)(0);
2115
+ const runtimeInfo = readinessEnvelope.runtime;
2116
+ const readinessSnapshot = readinessEnvelope.snapshot;
2117
+ (0, react.useEffect)(() => {
2118
+ const delay = readinessExpiryDelay(readinessEnvelope.expiresAt);
2119
+ if (delay === null) return void 0;
2120
+ const expected = readinessEnvelope;
2121
+ const timer = setTimeout(() => setReadinessEnvelope((current) => current === expected ? {} : current), delay);
2122
+ return () => clearTimeout(timer);
2123
+ }, [readinessEnvelope]);
1503
2124
  const toggleSection = (sectionId) => {
1504
2125
  setExpandedSections((current) => ({
1505
2126
  ...current,
@@ -1544,7 +2165,10 @@ function WorkersPanel({ ctx }) {
1544
2165
  /** Every request carries the active locale: the panel is the authority on it
1545
2166
  * (DSH's setting may be unset), and the hub localizes its own strings from it. */
1546
2167
  const withLang = (0, react.useCallback)((path) => `${API}${path}${path.includes("?") ? "&" : "?"}lang=${locale === "zh" ? "zh" : "en"}`, [locale]);
1547
- const get = (0, react.useCallback)(async (path) => readJson(await fetch(withLang(path), { cache: "no-store" }), path), [readJson, withLang]);
2168
+ const get = (0, react.useCallback)(async (path, timeoutMs = 8e3) => readJson(await fetch(withLang(path), {
2169
+ cache: "no-store",
2170
+ signal: AbortSignal.timeout(timeoutMs)
2171
+ }), path), [readJson, withLang]);
1548
2172
  const post = (0, react.useCallback)(async (path, body) => readJson(await fetch(withLang(path), {
1549
2173
  method: "POST",
1550
2174
  headers: { "content-type": "application/json" },
@@ -1569,7 +2193,6 @@ function WorkersPanel({ ctx }) {
1569
2193
  }
1570
2194
  };
1571
2195
  const [bridgeStatus, runtime] = await Promise.all([readOptional("/bridge-status"), readOptional("/runtime")]);
1572
- setRuntimeInfo(runtime);
1573
2196
  setSurface(classifyCrewSurface({
1574
2197
  bridgeStatus,
1575
2198
  runtime
@@ -1579,16 +2202,17 @@ function WorkersPanel({ ctx }) {
1579
2202
  detectSurface();
1580
2203
  }, [detectSurface]);
1581
2204
  const refreshAll = (0, react.useCallback)(async () => {
2205
+ const generation = ++readinessGeneration.current;
2206
+ setReadinessEnvelope({});
1582
2207
  try {
1583
- const [j, s, c, pr, pi, ph, cr, ext] = await Promise.all([
2208
+ const [j, s, c, pr, pi, ph, cr] = await Promise.all([
1584
2209
  get("/jobs"),
1585
2210
  get("/install/status"),
1586
2211
  get("/config"),
1587
2212
  get("/presets"),
1588
2213
  get("/providers").catch(() => null),
1589
2214
  get("/provider-health").catch(() => null),
1590
- get("/credential-references").catch(() => null),
1591
- get("/extension").catch(() => null)
2215
+ get("/credential-references").catch(() => null)
1592
2216
  ]);
1593
2217
  if (j.ok) setJobs(j.jobs ?? []);
1594
2218
  if (s.ok) setStatus(s.status);
@@ -1606,19 +2230,44 @@ function WorkersPanel({ ctx }) {
1606
2230
  setCredentialRefs(cr.records ?? []);
1607
2231
  setCredentialUnverified(cr.unverified_purges ?? []);
1608
2232
  }
1609
- if (ext?.ok) setReadinessSnapshot(ext.extension?.readiness_snapshot ?? void 0);
1610
- } catch {}
2233
+ } catch {
2234
+ if (generation === readinessGeneration.current) setReadinessEnvelope({});
2235
+ }
1611
2236
  }, [get]);
1612
2237
  (0, react.useEffect)(() => {
1613
2238
  if (!surfaceResponsibilities(surface).fullControlPlane) return void 0;
1614
2239
  refreshAll();
1615
- const timer = setInterval(() => {
1616
- Promise.all([get("/jobs"), get("/provider-health").catch(() => null)]).then(([j, health]) => {
1617
- if (j.ok) setJobs(j.jobs ?? []);
2240
+ let cancelled = false;
2241
+ let timer;
2242
+ const poll = async () => {
2243
+ const generation = ++readinessGeneration.current;
2244
+ try {
2245
+ const ext = await get("/extension", 5e3);
2246
+ if (!cancelled && generation === readinessGeneration.current) {
2247
+ const accepted = acceptReadinessResponse(ext, {
2248
+ generation,
2249
+ latestGeneration: readinessGeneration.current
2250
+ });
2251
+ if (accepted.accepted) setReadinessEnvelope(accepted.envelope);
2252
+ }
2253
+ } catch {
2254
+ if (!cancelled && generation === readinessGeneration.current) setReadinessEnvelope({});
2255
+ }
2256
+ Promise.all([get("/jobs", 5e3).catch(() => null), get("/provider-health", 5e3).catch(() => null)]).then(([j, health]) => {
2257
+ if (cancelled) return;
2258
+ if (j?.ok) setJobs(j.jobs ?? []);
1618
2259
  if (health?.ok) setProviderHealth(health.health ?? []);
1619
2260
  }).catch(() => {});
1620
- }, 3e3);
1621
- return () => clearInterval(timer);
2261
+ if (!cancelled) timer = setTimeout(() => {
2262
+ poll();
2263
+ }, 3e3);
2264
+ };
2265
+ poll();
2266
+ return () => {
2267
+ cancelled = true;
2268
+ readinessGeneration.current += 1;
2269
+ if (timer) clearTimeout(timer);
2270
+ };
1622
2271
  }, [
1623
2272
  surface,
1624
2273
  refreshAll,
@@ -2252,7 +2901,7 @@ function WorkersPanel({ ctx }) {
2252
2901
  }),
2253
2902
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2254
2903
  style: S.chip(ready),
2255
- children: ready ? `● ${copy.ready}` : `○ ${copy.notReady}`
2904
+ children: ready ? `● ${copy.integrationReady}` : `○ ${copy.notReady}`
2256
2905
  }),
2257
2906
  extra,
2258
2907
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
@@ -2316,6 +2965,7 @@ function WorkersPanel({ ctx }) {
2316
2965
  } } });
2317
2966
  };
2318
2967
  const modelActivity = aggregateModelInvocations(jobs);
2968
+ const modelState = modelCallabilityState(readinessSnapshot?.model_callability, runtimeInfo);
2319
2969
  const currentSurfaceResponsibilities = surfaceResponsibilities(surface);
2320
2970
  const hostReadiness = projectHostReadiness({
2321
2971
  installStatus: status,
@@ -2329,6 +2979,7 @@ function WorkersPanel({ ctx }) {
2329
2979
  runtime: runtimeInfo
2330
2980
  });
2331
2981
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2982
+ className: "dsh-crew-ui",
2332
2983
  style: {
2333
2984
  display: "flex",
2334
2985
  flexDirection: "column",
@@ -2337,102 +2988,39 @@ function WorkersPanel({ ctx }) {
2337
2988
  lineHeight: 1.55
2338
2989
  },
2339
2990
  children: [
2340
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2341
- style: {
2342
- border: "1px solid rgba(128,128,128,0.24)",
2343
- borderRadius: 12,
2344
- padding: "14px 15px",
2345
- display: "flex",
2346
- gap: 14,
2347
- alignItems: "center",
2348
- flexWrap: "wrap",
2349
- background: "linear-gradient(135deg, rgba(74,158,255,0.10), rgba(128,128,128,0.025) 56%)"
2350
- },
2351
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2352
- style: {
2353
- minWidth: 240,
2354
- flex: 1
2355
- },
2356
- children: [
2357
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2358
- style: {
2359
- fontSize: 20,
2360
- fontWeight: 680,
2361
- letterSpacing: "-0.01em"
2362
- },
2363
- children: copy.title
2364
- }),
2365
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2366
- style: {
2367
- opacity: .7,
2368
- fontSize: 12.5,
2369
- marginTop: 2
2370
- },
2371
- children: copy.intro
2372
- }),
2373
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2374
- style: {
2375
- display: "flex",
2376
- gap: 6,
2377
- marginTop: 8,
2378
- flexWrap: "wrap"
2379
- },
2380
- children: [
2381
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2382
- style: S.chip(!!status?.codex?.ready),
2383
- children: ["Codex ", status?.codex?.ready ? "READY" : "CHECK"]
2384
- }),
2385
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2386
- style: S.chip(!!status?.claude?.ready),
2387
- children: ["Claude ", status?.claude?.ready ? "READY" : "CHECK"]
2388
- }),
2389
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2390
- style: S.chip(!!status?.zcode?.ready),
2391
- children: ["ZCode ", status?.zcode?.ready ? "READY" : "CHECK"]
2392
- }),
2393
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2394
- style: S.chip(jobs.some((job) => job.status === "running")),
2395
- children: [jobs.filter((job) => job.status === "running").length, " running"]
2396
- })
2397
- ]
2398
- })
2399
- ]
2400
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2401
- style: {
2402
- display: "flex",
2403
- flexDirection: "column",
2404
- alignItems: "flex-end",
2405
- gap: 3
2406
- },
2407
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2408
- href: CREW_HARNESS_URL,
2409
- target: "_blank",
2410
- rel: "noopener noreferrer",
2411
- style: {
2412
- ...S.btn,
2413
- textDecoration: "none",
2414
- fontWeight: 650,
2415
- padding: "7px 12px",
2416
- borderColor: "rgba(74,158,255,0.55)",
2417
- background: "rgba(74,158,255,0.10)"
2418
- },
2419
- children: copy.openHarness
2420
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2421
- style: {
2422
- fontSize: 10.5,
2423
- opacity: .52
2424
- },
2425
- children: copy.harnessHint
2426
- })]
2427
- })]
2991
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelStyles, {}),
2992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PanelHeader, {
2993
+ title: copy.title,
2994
+ eyebrow: copy.surfaceLabel,
2995
+ description: copy.intro,
2996
+ href: CREW_CONTROL_PLANE_URL,
2997
+ linkText: copy.openOfficial,
2998
+ children: [
2999
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3000
+ style: S.chip(status?.codex?.installed === true),
3001
+ children: `Codex ${status?.codex?.installed === true ? copy.installed : copy.notInstalled}`
3002
+ }),
3003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3004
+ style: S.chip(status?.claude?.installed === true),
3005
+ children: `Claude ${status?.claude?.installed === true ? copy.installed : copy.notInstalled}`
3006
+ }),
3007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3008
+ style: S.chip(status?.zcode?.installed === true),
3009
+ children: `ZCode ${status?.zcode?.installed === true ? copy.installed : copy.notInstalled}`
3010
+ }),
3011
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3012
+ style: readinessChip(modelState),
3013
+ children: modelState === READINESS_STATES.READY ? copy.modelCallable : modelState === READINESS_STATES.UNKNOWN ? copy.modelUnknown : copy.modelNotCallable
3014
+ }),
3015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3016
+ style: S.chip(jobs.some((job) => job.status === "running")),
3017
+ children: copy.runningCount(jobs.filter((job) => job.status === "running").length)
3018
+ })
3019
+ ]
2428
3020
  }),
3021
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryPanel, { locale }),
2429
3022
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2430
- style: {
2431
- display: "flex",
2432
- alignItems: "center",
2433
- gap: 7,
2434
- marginTop: 5
2435
- },
3023
+ className: "crew-config-toolbar",
2436
3024
  children: [
2437
3025
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2438
3026
  style: {
@@ -2465,7 +3053,11 @@ function WorkersPanel({ ctx }) {
2465
3053
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
2466
3054
  sectionId: "integrations",
2467
3055
  title: copy.sectionNames.integrations,
2468
- summary: sectionSummary(status?.claude?.ready ? "Claude READY" : "Claude CHECK", `${status?.codex?.ready ? "Codex READY" : "Codex CHECK"} · ${status?.zcode?.ready ? "ZCode READY" : "ZCode CHECK"}`),
3056
+ summary: sectionSummary(...[
3057
+ "Codex",
3058
+ "Claude",
3059
+ "ZCode"
3060
+ ].map((name) => `${name} ${status?.[name.toLowerCase()]?.installed ? copy.installed : copy.notInstalled}`), modelState === READINESS_STATES.READY ? copy.modelCallable : copy.modelNotCallable),
2469
3061
  expanded: !!expandedSections.integrations,
2470
3062
  onToggle: () => toggleSection("integrations"),
2471
3063
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4730,7 +5322,7 @@ function WorkersPanel({ ctx }) {
4730
5322
  children: [
4731
5323
  entry.selection_sources.join(" · "),
4732
5324
  " · ",
4733
- entry.roles.join(" / ")
5325
+ entry.roles.length > 0 ? entry.roles.join(" / ") : copy.never
4734
5326
  ]
4735
5327
  })]
4736
5328
  }),