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

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 (83) 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 +5434 -4844
  23. package/official-web-bridge/lib/client.js +567 -479
  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 +86 -64
  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/config-readiness.mjs +181 -115
  39. package/src/extension-contract.mjs +44 -26
  40. package/src/history/admission-gate.mjs +51 -0
  41. package/src/history/archive-store.mjs +261 -0
  42. package/src/history/cleanup-plan.mjs +64 -0
  43. package/src/history/http.mjs +32 -0
  44. package/src/history/operation.mjs +86 -0
  45. package/src/history/runner.mjs +51 -0
  46. package/src/history/runtime.mjs +28 -0
  47. package/src/history/service.mjs +137 -0
  48. package/src/history/state.mjs +27 -0
  49. package/src/hub/index.mjs +141 -24
  50. package/src/hub-client.mjs +17 -15
  51. package/src/image-output.mjs +71 -0
  52. package/src/install/install-legacy.mjs +126 -23
  53. package/src/install/npx-lifecycle.mjs +373 -64
  54. package/src/install/official-frontend-assets.mjs +78 -0
  55. package/src/install/payload-content.mjs +88 -0
  56. package/src/install/windows-startup.mjs +107 -19
  57. package/src/install/windows-supervisor-adapter.mjs +443 -0
  58. package/src/install/windows-supervisor-lifecycle.mjs +782 -0
  59. package/src/install/zcode.mjs +53 -14
  60. package/src/job-contracts.mjs +37 -31
  61. package/src/jobs.mjs +2 -2
  62. package/src/mcp-runtime.mjs +74 -9
  63. package/src/model-callability-contract.mjs +79 -0
  64. package/src/multimodal.mjs +0 -0
  65. package/src/official-web-bridge.mjs +0 -2
  66. package/src/provider-health.mjs +130 -124
  67. package/src/readiness-matrix.mjs +20 -15
  68. package/src/runtime-identity-contract.mjs +34 -0
  69. package/src/runtime-identity.mjs +14 -4
  70. package/src/runtime-readiness-snapshot.mjs +285 -87
  71. package/src/server.mjs +86 -76
  72. package/src/standalone-sdk.mjs +23 -0
  73. package/src/supervisor/restart-request.mjs +64 -10
  74. package/src/workflow-runtime.mjs +28 -13
  75. package/windows/start-dsh-crew.cmd +57 -57
  76. package/windows/start-dsh-crew.ps1 +314 -51
  77. package/windows/supervisor-control.ps1 +467 -0
  78. package/zcode/AGENTS.md +4 -1
  79. package/zcode/agents/ds-reviewer.md +31 -36
  80. package/zcode/agents/ds-worker.md +31 -39
  81. package/zcode/commands/dsh-config.md +17 -6
  82. package/zcode/commands/dsh-status.md +5 -4
  83. package/.mcp.json +0 -8
@@ -2,7 +2,7 @@
2
2
  // global worker configuration, and the live jobs table.
3
3
  // Talks only to the plugin's own loopback routes.
4
4
 
5
- import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';
5
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import {
8
8
  openSections,
@@ -12,13 +12,16 @@ import {
12
12
  } from './collapsible-sections.mjs';
13
13
  import { ActivationSummary } from './activation-summary';
14
14
  import { projectHostReadiness, READINESS_STATES } from './host-readiness.mjs';
15
+ import { modelCallabilityState } from './model-callability-view.mjs';
16
+ import { acceptReadinessResponse, readinessExpiryDelay } from './readiness-envelope.mjs';
15
17
  import { CREW_UI_SURFACES, classifyCrewSurface, surfaceResponsibilities } from './surface-detection.mjs';
16
18
  import { aggregateModelInvocations } from './task-telemetry.mjs';
19
+ import { PanelHeader, PanelStyles } from './panel-chrome';
20
+ import { HistoryPanel } from './history-panel';
17
21
 
18
22
  export const inject = ['slots', 'locale'];
19
23
 
20
24
  const API = '/_dsh/dsh-crew';
21
- const CREW_HARNESS_URL = 'http://127.0.0.1:3210/';
22
25
  const CREW_CONTROL_PLANE_URL = 'http://127.0.0.1:3080/';
23
26
 
24
27
  type AdaptiveConfig = { enabled: boolean; window_size: number; min_samples: number };
@@ -43,9 +46,11 @@ const COPY = {
43
46
  zh: {
44
47
  label: 'DSH Crew',
45
48
  title: 'DSH Crew',
46
- intro: '日常 Crew 控制台:在一个入口里配置编排、角色、模型路由与宿主集成,并跟踪真实 Worker / Reviewer 任务。',
49
+ openOfficial: '打开 3080 官方界面 →',
50
+ intro: '管理工作流、模型路由与宿主集成。展开分区查看和修改设置。',
51
+ surfaceLabel: '3210 / 高级配置',
47
52
  integrations: '集成',
48
- installed: '已安装', notInstalled: '未安装', ready: '可调用', notReady: '未就绪', hud: 'HUD 段',
53
+ installed: '已安装', notInstalled: '未安装', ready: '集成就绪', integrationReady: '集成就绪', modelCallable: '模型可调用', modelNotCallable: '模型未验证', modelUnknown: '模型未知', notReady: '未就绪', hud: 'HUD 段',
49
54
  install: '安装', update: '更新', restore: '还原',
50
55
  confirmRestore: (name: string) => `确定从 ${name} 移除 dsh-crew 集成?(settings 会先备份)`,
51
56
  globalConfig: '全局配置',
@@ -53,11 +58,10 @@ const COPY = {
53
58
  modelCount: (count: number) => `${count} 个模型`, providerCount: (count: number) => `${count} 个 Provider`,
54
59
  jobCount: (count: number) => `${count} 个任务`, runningCount: (count: number) => `${count} 个运行中`,
55
60
  sectionNames: { integrations: 'Codex / Claude / ZCode 集成状态', workflow: 'Crew 工作流设置', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: '模型优先级与派发', adaptive: '自适应路由', runtime: '运行 / 生效边界', multimodal: '视觉与生图', harnessProviders: 'Harness Providers', providers: '多模态适配器', jobs: '任务状态' },
56
- openHarness: '打开 3210 Crew Harness →',
57
- harnessHint: '底层 Provider、Harness Models 与运行时配置',
61
+ harnessHint: 'DeepSeek 官方 Harness 的 dsh-crew profile:Provider、Harness Models 与运行时配置',
58
62
  hostReadiness: '宿主集成就绪度', hostReadinessHint: '只使用结构化安装与运行时证据;缺少证据不会显示 READY。',
59
- readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew Harness', official_bridge: 'Official bridge' },
60
- readinessStates: { READY: 'READY', DEGRADED: 'DEGRADED', UNAVAILABLE: 'UNAVAILABLE', UNKNOWN: 'UNKNOWN' },
63
+ readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew plugin profile', official_bridge: 'Official bridge' },
64
+ readinessStates: { READY: 'READY', DEGRADED: 'DEGRADED', UNAVAILABLE: 'UNAVAILABLE', UNKNOWN: 'UNKNOWN', NOT_APPLICABLE: 'N/A' },
61
65
  globalHint: '修改即时保存到 ~/.config/dsh-crew/config.json;CC / Codex 的新会话自动读取为默认值(会话内可用 /dsh-crew:config 临时覆盖)。',
62
66
  orchestration: 'Agent 编排',
63
67
  enableSubagents: '启用子 Agent',
@@ -103,7 +107,7 @@ const COPY = {
103
107
  presetFlash: 'flash 模式', presetPro: 'pro 模式',
104
108
  multimodal: '多模态',
105
109
  visionProvider: '视觉 provider', visionModel: '视觉模型', imagegenProvider: '生图 provider',
106
- harnessProviders: 'Harness Providers', harnessProviderHint: '3210 Crew Harness 的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。',
110
+ harnessProviders: 'Harness Providers', harnessProviderHint: 'DeepSeek 官方 Harness 的 3210 dsh-crew profile 中的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。',
107
111
  providerState: (state: string) => `状态:${state}`, providerModels: (count: number) => `${count} 个模型`, providerJobs: (count: number) => `${count} 个运行任务`,
108
112
  providerOfficialBuiltin: '· 官方内置(不可删除)', providerSourceUnresolved: '· 来源未解析(安全锁定)',
109
113
  providerLayerMigration: '检测到旧版 profile Provider', providerLayerMigrationHint: '这些 Provider 仍在 Crew profile/base 层,所以 3210 原生页面不会显示删除。请先按迁移计划提升到 Harness 用户设置层;不会复制密钥。', providerLayerMigrationPlan: '迁移计划', providerLayerMigrate: '迁移到用户层', providerLayerMigrating: '迁移中…', providerLayerMigrationConfirm: '确认把这个 Provider 移到 Harness 用户设置层?会先保存可回滚快照,不会复制密钥值。', providerLayerRollback: '回滚迁移',
@@ -184,9 +188,11 @@ const COPY = {
184
188
  en: {
185
189
  label: 'DSH Crew',
186
190
  title: 'DSH Crew',
187
- intro: 'The daily Crew console: configure orchestration, roles, model routing, and host integrations in one place while tracking real Worker / Reviewer jobs.',
191
+ intro: 'Manage workflows, model routing and host integrations. Expand a section to edit its settings.',
192
+ surfaceLabel: '3210 / ADVANCED SETTINGS',
193
+ openOfficial: 'Open official frontend (3080) →',
188
194
  integrations: 'Integrations',
189
- installed: 'installed', notInstalled: 'not installed', ready: 'ready', notReady: 'not ready', hud: 'HUD segment',
195
+ installed: 'installed', notInstalled: 'not installed', ready: 'integration ready', integrationReady: 'integration ready', modelCallable: 'model callable', modelNotCallable: 'model not verified', modelUnknown: 'model unknown', notReady: 'not ready', hud: 'HUD segment',
190
196
  install: 'Install', update: 'Update', restore: 'Restore',
191
197
  confirmRestore: (name: string) => `Remove the dsh-crew integration from ${name}? (settings are backed up first)`,
192
198
  globalConfig: 'Global configuration',
@@ -194,11 +200,10 @@ const COPY = {
194
200
  modelCount: (count: number) => `${count} models`, providerCount: (count: number) => `${count} providers`,
195
201
  jobCount: (count: number) => `${count} jobs`, runningCount: (count: number) => `${count} running`,
196
202
  sectionNames: { integrations: 'Codex / Claude / ZCode integration status', workflow: 'Crew workflow settings', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: 'Model priority & dispatch', adaptive: 'Adaptive routing', runtime: 'Runtime / activation boundaries', multimodal: 'Vision & image generation', harnessProviders: 'Harness Providers', providers: 'Multimodal adapters', jobs: 'Task status' },
197
- openHarness: 'Open 3210 Crew Harness →',
198
- harnessHint: 'Low-level providers, Harness Models, and runtime configuration',
203
+ harnessHint: 'The official DeepSeek Harness dsh-crew profile: providers, Harness Models, and runtime configuration',
199
204
  hostReadiness: 'Host integration readiness', hostReadinessHint: 'Uses structured installer and runtime evidence only; missing evidence is never READY.',
200
- readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew Harness', official_bridge: 'Official bridge' },
201
- readinessStates: { READY: 'READY', DEGRADED: 'DEGRADED', UNAVAILABLE: 'UNAVAILABLE', UNKNOWN: 'UNKNOWN' },
205
+ readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew plugin profile', official_bridge: 'Official bridge' },
206
+ readinessStates: { READY: 'READY', DEGRADED: 'DEGRADED', UNAVAILABLE: 'UNAVAILABLE', UNKNOWN: 'UNKNOWN', NOT_APPLICABLE: 'N/A' },
202
207
  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).',
203
208
  orchestration: 'Agent orchestration',
204
209
  enableSubagents: 'Enable Subagents',
@@ -244,7 +249,7 @@ const COPY = {
244
249
  presetFlash: 'flash preset', presetPro: 'pro preset',
245
250
  multimodal: 'Multimodal',
246
251
  visionProvider: 'Vision provider', visionModel: 'Vision model', imagegenProvider: 'Image-gen provider',
247
- harnessProviders: 'Harness Providers', harnessProviderHint: 'Live Provider registration and lifecycle in the 3210 Crew Harness; separate from vision / image-gen adapters.',
252
+ harnessProviders: 'Harness Providers', harnessProviderHint: 'Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210; separate from vision / image-gen adapters.',
248
253
  providerState: (state: string) => `State: ${state}`, providerModels: (count: number) => `${count} models`, providerJobs: (count: number) => `${count} running jobs`,
249
254
  providerOfficialBuiltin: '· official built-in (immutable)', providerSourceUnresolved: '· source unresolved (locked)',
250
255
  providerLayerMigration: 'Legacy profile Providers detected', providerLayerMigrationHint: 'These Providers still exist in the Crew profile/base layer, so the native 3210 page cannot show Delete. Promote them to Harness user settings with an explicit plan; keys are never copied.', providerLayerMigrationPlan: 'Migration plan', providerLayerMigrate: 'Migrate to user layer', providerLayerMigrating: 'Migrating…', providerLayerMigrationConfirm: 'Move this Provider into Harness user settings? A rollback snapshot is saved first; credential values are never copied.', providerLayerRollback: 'Rollback migration',
@@ -430,10 +435,12 @@ function CollapsibleSection({ sectionId, title, summary, expanded, onToggle, chi
430
435
  const bodyId = `dsh-crew-section-${sectionId}`;
431
436
  return (
432
437
  <section style={S.sectionShell} data-section-id={sectionId}>
433
- <button type="button" style={S.sectionButton} aria-expanded={expanded} aria-controls={bodyId} onClick={onToggle}>
438
+ <button type="button" className="crew-section-trigger" style={S.sectionButton} aria-expanded={expanded} aria-controls={bodyId} onClick={onToggle}>
434
439
  <span aria-hidden="true" style={{ width: 14, opacity: 0.62, transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 140ms ease' }}>›</span>
435
- <span style={{ fontWeight: 650, fontSize: 13, minWidth: 92 }}>{title}</span>
436
- <span title={summary} style={{ flex: 1, minWidth: 0, fontSize: 11.5, opacity: 0.58, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{summary}</span>
440
+ <span className="crew-section-heading">
441
+ <span className="crew-section-title">{title}</span>
442
+ <span className="crew-section-summary">{summary}</span>
443
+ </span>
437
444
  </button>
438
445
  <div id={bodyId} hidden={!expanded} style={expanded ? S.sectionBody : undefined}>{children}</div>
439
446
  </section>
@@ -463,7 +470,8 @@ function formatTimestamp(value: string | null, locale: string) {
463
470
  function readinessChip(state: string) {
464
471
  const color = state === READINESS_STATES.READY ? '#3fb950'
465
472
  : state === READINESS_STATES.DEGRADED ? '#c98735'
466
- : state === READINESS_STATES.UNAVAILABLE ? '#f85149' : 'inherit';
473
+ : state === READINESS_STATES.UNAVAILABLE ? '#f85149'
474
+ : state === READINESS_STATES.NOT_APPLICABLE ? '#7d8590' : 'inherit';
467
475
  return {
468
476
  fontSize: 10.5, fontWeight: 650, padding: '1px 7px', borderRadius: 99,
469
477
  border: `1px solid ${state === READINESS_STATES.UNKNOWN ? 'rgba(128,128,128,0.35)' : color}`,
@@ -477,8 +485,8 @@ function MinimalCrewPanel({ locale, surface, runtime }: { locale: string; surfac
477
485
  const title = native ? (zh ? 'DSH Crew Runtime' : 'DSH Crew Runtime') : (zh ? 'DSH Crew Surface 未确认' : 'DSH Crew surface not verified');
478
486
  const hint = native
479
487
  ? (zh
480
- ? '这里是隔离的 3210 Crew Harness。请使用 Harness 原生菜单管理 Provider、Harness Models、Agent 预设与底层配置;Crew 编排和宿主集成统一在 3080 管理。'
481
- : '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.')
488
+ ? '这里是 DeepSeek 官方 Harness 的专用 dsh-crew profile,DSH Crew 作为插件加载其中。请使用 Harness 原生菜单管理 Provider、Harness Models、Agent 预设与底层配置。'
489
+ : '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.')
482
490
  : (zh
483
491
  ? '无法用结构化后端证据确认当前界面,因此已保守隐藏 Crew 编排、任务与宿主集成功能。'
484
492
  : 'Structured backend evidence could not identify this surface, so Crew orchestration, jobs, and host integrations are conservatively hidden.');
@@ -495,13 +503,13 @@ function MinimalCrewPanel({ locale, surface, runtime }: { locale: string; surfac
495
503
  {runtimeReady ? `Runtime ${runtime.runtime_version ?? 'READY'}` : 'Runtime UNKNOWN'}
496
504
  </span>
497
505
  <span style={readinessChip(native ? READINESS_STATES.READY : READINESS_STATES.UNKNOWN)}>
498
- {native ? '3210 · isolated' : 'surface · unknown'}
506
+ {native ? '3210 · official profile + Crew plugin' : 'surface · unknown'}
499
507
  </span>
500
508
  </div>
501
509
  <a href={CREW_CONTROL_PLANE_URL} target="_blank" rel="noopener noreferrer" style={{
502
510
  ...S.btn, alignSelf: 'flex-start', textDecoration: 'none', fontWeight: 650, padding: '7px 12px',
503
511
  borderColor: 'rgba(74,158,255,0.55)', background: 'rgba(74,158,255,0.08)',
504
- }}>{zh ? '打开 3080 DSH Crew 控制台 →' : 'Open the 3080 DSH Crew console →'}</a>
512
+ }}>{zh ? '打开官方 Harness 界面(3080) →' : 'Open official Harness (3080) →'}</a>
505
513
  </div>
506
514
  </div>
507
515
  );
@@ -544,8 +552,18 @@ function WorkersPanel({ ctx }: { ctx: any }) {
544
552
  const [testResult, setTestResult] = useState<{ key: string; ok?: boolean; steps?: any[]; error?: string; busy: boolean } | null>(null);
545
553
  const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>(() => readSectionState(sectionStorage()));
546
554
  const [surface, setSurface] = useState<string>('detecting');
547
- const [runtimeInfo, setRuntimeInfo] = useState<any>(undefined);
548
- const [readinessSnapshot, setReadinessSnapshot] = useState<any>(undefined);
555
+ const [readinessEnvelope, setReadinessEnvelope] = useState<{ runtime?: any; snapshot?: any; expiresAt?: number | null }>({});
556
+ const readinessGeneration = useRef(0);
557
+ const runtimeInfo = readinessEnvelope.runtime;
558
+ const readinessSnapshot = readinessEnvelope.snapshot;
559
+
560
+ useEffect(() => {
561
+ const delay = readinessExpiryDelay(readinessEnvelope.expiresAt);
562
+ if (delay === null) return undefined;
563
+ const expected = readinessEnvelope;
564
+ const timer = setTimeout(() => setReadinessEnvelope((current) => current === expected ? {} : current), delay);
565
+ return () => clearTimeout(timer);
566
+ }, [readinessEnvelope]);
549
567
 
550
568
  const toggleSection = (sectionId: string) => {
551
569
  setExpandedSections((current) => ({ ...current, [sectionId]: !current[sectionId] }));
@@ -594,7 +612,7 @@ function WorkersPanel({ ctx }: { ctx: any }) {
594
612
  /** Every request carries the active locale: the panel is the authority on it
595
613
  * (DSH's setting may be unset), and the hub localizes its own strings from it. */
596
614
  const withLang = useCallback((path: string) => `${API}${path}${path.includes('?') ? '&' : '?'}lang=${locale === 'zh' ? 'zh' : 'en'}`, [locale]);
597
- const get = useCallback(async (path: string) => readJson(await fetch(withLang(path), { cache: 'no-store' }), path), [readJson, withLang]);
615
+ const get = useCallback(async (path: string, timeoutMs = 8_000) => readJson(await fetch(withLang(path), { cache: 'no-store', signal: AbortSignal.timeout(timeoutMs) }), path), [readJson, withLang]);
598
616
  const post = useCallback(async (path: string, body: any) => readJson(await fetch(withLang(path), {
599
617
  method: 'POST', headers: { 'content-type': 'application/json' },
600
618
  body: JSON.stringify({ ...body, lang: locale === 'zh' ? 'zh' : 'en' }),
@@ -613,17 +631,18 @@ function WorkersPanel({ ctx }: { ctx: any }) {
613
631
  readOptional('/bridge-status'),
614
632
  readOptional('/runtime'),
615
633
  ]);
616
- setRuntimeInfo(runtime);
617
634
  setSurface(classifyCrewSurface({ bridgeStatus, runtime }));
618
635
  }, []);
619
636
 
620
637
  useEffect(() => { void detectSurface(); }, [detectSurface]);
621
638
 
622
639
  const refreshAll = useCallback(async () => {
640
+ const generation = ++readinessGeneration.current;
641
+ setReadinessEnvelope({});
623
642
  try {
624
- const [j, s, c, pr, pi, ph, cr, ext] = await Promise.all([
643
+ const [j, s, c, pr, pi, ph, cr] = await Promise.all([
625
644
  get('/jobs'), get('/install/status'), get('/config'), get('/presets'),
626
- get('/providers').catch(() => null), get('/provider-health').catch(() => null), get('/credential-references').catch(() => null), get('/extension').catch(() => null),
645
+ get('/providers').catch(() => null), get('/provider-health').catch(() => null), get('/credential-references').catch(() => null),
627
646
  ]);
628
647
  if (j.ok) setJobs(j.jobs ?? []);
629
648
  if (s.ok) setStatus(s.status);
@@ -635,21 +654,37 @@ function WorkersPanel({ ctx }: { ctx: any }) {
635
654
  if (pi?.ok) setProviderInventory(pi);
636
655
  if (ph?.ok) setProviderHealth(ph.health ?? []);
637
656
  if (cr?.ok) { setCredentialRefs(cr.records ?? []); setCredentialUnverified(cr.unverified_purges ?? []); }
638
- if (ext?.ok) setReadinessSnapshot(ext.extension?.readiness_snapshot ?? undefined);
639
- } catch { /* instance restarting */ }
657
+ } catch {
658
+ if (generation === readinessGeneration.current) setReadinessEnvelope({});
659
+ }
640
660
  }, [get]);
641
661
 
642
662
  useEffect(() => {
643
663
  const responsibilities = surfaceResponsibilities(surface);
644
664
  if (!responsibilities.fullControlPlane) return undefined;
645
665
  void refreshAll();
646
- const timer = setInterval(() => {
647
- void Promise.all([get('/jobs'), get('/provider-health').catch(() => null)]).then(([j, health]) => {
648
- if (j.ok) setJobs(j.jobs ?? []);
666
+ let cancelled = false;
667
+ let timer: ReturnType<typeof setTimeout> | undefined;
668
+ const poll = async () => {
669
+ const generation = ++readinessGeneration.current;
670
+ try {
671
+ const ext = await get('/extension', 5_000);
672
+ if (!cancelled && generation === readinessGeneration.current) {
673
+ const accepted = acceptReadinessResponse(ext, { generation, latestGeneration: readinessGeneration.current });
674
+ if (accepted.accepted) setReadinessEnvelope(accepted.envelope);
675
+ }
676
+ } catch {
677
+ if (!cancelled && generation === readinessGeneration.current) setReadinessEnvelope({});
678
+ }
679
+ void Promise.all([get('/jobs', 5_000).catch(() => null), get('/provider-health', 5_000).catch(() => null)]).then(([j, health]) => {
680
+ if (cancelled) return;
681
+ if (j?.ok) setJobs(j.jobs ?? []);
649
682
  if (health?.ok) setProviderHealth(health.health ?? []);
650
683
  }).catch(() => {});
651
- }, 3000);
652
- return () => clearInterval(timer);
684
+ if (!cancelled) timer = setTimeout(() => { void poll(); }, 3_000);
685
+ };
686
+ void poll();
687
+ return () => { cancelled = true; readinessGeneration.current += 1; if (timer) clearTimeout(timer); };
653
688
  }, [surface, refreshAll, get]);
654
689
 
655
690
  const refreshHarnessModels = useCallback(async () => {
@@ -1060,7 +1095,7 @@ function WorkersPanel({ ctx }: { ctx: any }) {
1060
1095
  <div style={S.card}>
1061
1096
  <span style={{ fontWeight: 600, fontSize: 13, minWidth: 92 }}>{name}</span>
1062
1097
  <span style={S.chip(installed)}>{installed ? `● ${copy.installed}` : `○ ${copy.notInstalled}`}</span>
1063
- <span style={S.chip(ready)}>{ready ? `● ${copy.ready}` : `○ ${copy.notReady}`}</span>
1098
+ <span style={S.chip(ready)}>{ready ? `● ${copy.integrationReady}` : `○ ${copy.notReady}`}</span>
1064
1099
  {extra}
1065
1100
  <span style={{ flex: 1 }} />
1066
1101
  {!installed && <button style={S.btn} title={tips.install} disabled={busy} onClick={() => { void act(installTarget); }}>{copy.install}</button>}
@@ -1087,6 +1122,7 @@ function WorkersPanel({ ctx }: { ctx: any }) {
1087
1122
  void applyPatch({ worker: { model_policy: { adaptive: next, ordering } } });
1088
1123
  };
1089
1124
  const modelActivity = aggregateModelInvocations(jobs);
1125
+ const modelState = modelCallabilityState(readinessSnapshot?.model_callability, runtimeInfo);
1090
1126
  const currentSurfaceResponsibilities = surfaceResponsibilities(surface);
1091
1127
  const hostReadiness = projectHostReadiness({ installStatus: status, runtime: runtimeInfo, surface, readinessSnapshot });
1092
1128
 
@@ -1095,39 +1131,25 @@ function WorkersPanel({ ctx }: { ctx: any }) {
1095
1131
  }
1096
1132
 
1097
1133
  return (
1098
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8, fontSize: 13, lineHeight: 1.55 }}>
1099
- <div style={{
1100
- border: '1px solid rgba(128,128,128,0.24)', borderRadius: 12, padding: '14px 15px',
1101
- display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap',
1102
- background: 'linear-gradient(135deg, rgba(74,158,255,0.10), rgba(128,128,128,0.025) 56%)',
1103
- }}>
1104
- <div style={{ minWidth: 240, flex: 1 }}>
1105
- <div style={{ fontSize: 20, fontWeight: 680, letterSpacing: '-0.01em' }}>{copy.title}</div>
1106
- <div style={{ opacity: 0.7, fontSize: 12.5, marginTop: 2 }}>{copy.intro}</div>
1107
- <div style={{ display: 'flex', gap: 6, marginTop: 8, flexWrap: 'wrap' }}>
1108
- <span style={S.chip(!!status?.codex?.ready)}>Codex {status?.codex?.ready ? 'READY' : 'CHECK'}</span>
1109
- <span style={S.chip(!!status?.claude?.ready)}>Claude {status?.claude?.ready ? 'READY' : 'CHECK'}</span>
1110
- <span style={S.chip(!!status?.zcode?.ready)}>ZCode {status?.zcode?.ready ? 'READY' : 'CHECK'}</span>
1111
- <span style={S.chip(jobs.some((job) => job.status === 'running'))}>{jobs.filter((job) => job.status === 'running').length} running</span>
1112
- </div>
1113
- </div>
1114
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 3 }}>
1115
- <a href={CREW_HARNESS_URL} target="_blank" rel="noopener noreferrer" style={{
1116
- ...S.btn, textDecoration: 'none', fontWeight: 650, padding: '7px 12px',
1117
- borderColor: 'rgba(74,158,255,0.55)', background: 'rgba(74,158,255,0.10)',
1118
- }}>{copy.openHarness}</a>
1119
- <span style={{ fontSize: 10.5, opacity: 0.52 }}>{copy.harnessHint}</span>
1120
- </div>
1121
- </div>
1134
+ <div className="dsh-crew-ui" style={{ display: 'flex', flexDirection: 'column', gap: 8, fontSize: 13, lineHeight: 1.55 }}>
1135
+ <PanelStyles />
1136
+ <PanelHeader title={copy.title} eyebrow={copy.surfaceLabel} description={copy.intro} href={CREW_CONTROL_PLANE_URL} linkText={copy.openOfficial}>
1137
+ <span style={S.chip(status?.codex?.installed === true)}>{`Codex ${status?.codex?.installed === true ? copy.installed : copy.notInstalled}`}</span>
1138
+ <span style={S.chip(status?.claude?.installed === true)}>{`Claude ${status?.claude?.installed === true ? copy.installed : copy.notInstalled}`}</span>
1139
+ <span style={S.chip(status?.zcode?.installed === true)}>{`ZCode ${status?.zcode?.installed === true ? copy.installed : copy.notInstalled}`}</span>
1140
+ <span style={readinessChip(modelState)}>{modelState === READINESS_STATES.READY ? copy.modelCallable : modelState === READINESS_STATES.UNKNOWN ? copy.modelUnknown : copy.modelNotCallable}</span>
1141
+ <span style={S.chip(jobs.some((job) => job.status === 'running'))}>{copy.runningCount(jobs.filter((job) => job.status === 'running').length)}</span>
1142
+ </PanelHeader>
1143
+ <HistoryPanel locale={locale} />
1122
1144
 
1123
- <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginTop: 5 }}>
1145
+ <div className="crew-config-toolbar">
1124
1146
  <div style={{ ...S.section, margin: 0, flex: 1 }}>{copy.globalConfig}</div>
1125
1147
  <button type="button" style={{ ...S.btn, padding: '3px 9px' }} onClick={() => toggleAllSections(true)}>{copy.expandAll}</button>
1126
1148
  <button type="button" style={{ ...S.btn, padding: '3px 9px' }} onClick={() => toggleAllSections(false)}>{copy.collapseAll}</button>
1127
1149
  </div>
1128
1150
 
1129
1151
  <CollapsibleSection sectionId="integrations" title={copy.sectionNames.integrations}
1130
- summary={sectionSummary(status?.claude?.ready ? 'Claude READY' : 'Claude CHECK', `${status?.codex?.ready ? 'Codex READY' : 'Codex CHECK'} · ${status?.zcode?.ready ? 'ZCode READY' : 'ZCode CHECK'}`)}
1152
+ summary={sectionSummary(...['Codex', 'Claude', 'ZCode'].map((name) => `${name} ${status?.[name.toLowerCase()]?.installed ? copy.installed : copy.notInstalled}`), modelState === READINESS_STATES.READY ? copy.modelCallable : copy.modelNotCallable)}
1131
1153
  expanded={!!expandedSections.integrations} onToggle={() => toggleSection('integrations')}>
1132
1154
  <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
1133
1155
  <div style={S.block}>
@@ -0,0 +1,17 @@
1
+ // Pure client projection for the server-owned model_callability contract.
2
+ // The client must never infer current callability from historical matrix rows.
3
+
4
+ import { READINESS_STATES } from './host-readiness.mjs';
5
+ import { validateModelCallabilityV2 } from '../model-callability-contract.mjs';
6
+
7
+ export function modelCallabilityState(projection, runtime = null, now = Date.now()) {
8
+ if (!projection || typeof projection !== 'object' || typeof projection.overall !== 'string') {
9
+ return READINESS_STATES.UNKNOWN;
10
+ }
11
+ const validation = validateModelCallabilityV2({ projection, runtime, now });
12
+ if (!validation.ok) return READINESS_STATES.UNKNOWN;
13
+ if (validation.state === 'CALLABLE') return READINESS_STATES.READY;
14
+ if (validation.state === 'NOT_CALLABLE') return READINESS_STATES.UNAVAILABLE;
15
+ if (validation.state === 'STALE') return READINESS_STATES.DEGRADED;
16
+ return READINESS_STATES.UNKNOWN;
17
+ }
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ // Scoped to Crew panels: the official application's settings shell is untouched.
4
+ export function PanelStyles() {
5
+ return <style>{`
6
+ .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; }
7
+ .dsh-crew-ui *, .dsh-crew-ui *::before, .dsh-crew-ui *::after { box-sizing: border-box; }
8
+ .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; }
9
+ .dsh-crew-ui .crew-panel-heading { flex: 1 1 260px; min-width: 0; }
10
+ .dsh-crew-ui .crew-eyebrow { font-size: 10.5px; letter-spacing: .055em; opacity: .65; margin-bottom: 4px; }
11
+ .dsh-crew-ui .crew-panel-title { margin: 0; font-size: 19px; font-weight: 680; line-height: 1.3; letter-spacing: -.025em; }
12
+ .dsh-crew-ui .crew-panel-description { margin: 5px 0 0; font-size: 12px; opacity: .72; overflow-wrap: anywhere; }
13
+ .dsh-crew-ui .crew-panel-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
14
+ .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; }
15
+ .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; }
16
+ .dsh-crew-ui button:disabled { cursor: not-allowed; opacity: .4; }
17
+ .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); }
18
+ .dsh-crew-ui .crew-section-heading { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
19
+ .dsh-crew-ui .crew-section-title { font-weight: 650; font-size: 13px; }
20
+ .dsh-crew-ui .crew-section-summary { font-size: 11.5px; opacity: .65; overflow-wrap: anywhere; }
21
+ .dsh-crew-ui .crew-config-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin: 6px 0 2px; }
22
+ @media (prefers-color-scheme: dark) { .dsh-crew-ui { --crew-accent: #8ab4ff; } }
23
+ @media (prefers-reduced-motion: reduce) { .dsh-crew-ui * { transition: none !important; } }
24
+ `}</style>;
25
+ }
26
+
27
+ export function PanelHeader({ title, eyebrow, description, href, linkText, children }: {
28
+ title: string; eyebrow: string; description: string; href: string;
29
+ linkText: string; children?: ReactNode;
30
+ }) {
31
+ return <header className="crew-panel-header">
32
+ <div className="crew-panel-heading">
33
+ <div className="crew-eyebrow">{eyebrow}</div>
34
+ <h2 className="crew-panel-title">{title}</h2>
35
+ <p className="crew-panel-description">{description}</p>
36
+ {children && <div className="crew-panel-badges">{children}</div>}
37
+ </div>
38
+ <a className="crew-nav-link" href={href} target="_blank" rel="noopener noreferrer">{linkText}</a>
39
+ </header>;
40
+ }