@ran-sh/dsh-crew 1.1.0 → 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.
- package/.claude-plugin/marketplace.json +16 -17
- package/.claude-plugin/plugin.json +7 -1
- package/README.md +44 -15
- package/README.zh.md +37 -12
- package/agents/ds-flash.md +16 -18
- package/agents/ds-pro.md +16 -24
- package/agents/ds-reviewer.md +15 -15
- package/agents/ds-worker.md +16 -16
- package/bin/dsh-crew.mjs +16 -12
- package/codex/AGENTS.md +37 -85
- package/codex/agents/ds-flash.toml +18 -14
- package/codex/agents/ds-pro.toml +18 -15
- package/codex/agents/ds-reviewer.toml +17 -13
- package/codex/agents/ds-worker.toml +17 -13
- package/codex/prompts/dsh-config.md +17 -1
- package/codex/prompts/dsh-status.md +5 -1
- package/commands/config.md +16 -4
- package/commands/status.md +6 -2
- package/docs/installation.md +54 -9
- package/docs/readiness-matrix.md +14 -6
- package/docs/ui-surfaces.md +32 -17
- package/lib/client.js +5434 -4844
- package/official-web-bridge/lib/client.js +567 -479
- package/official-web-bridge/overlay-entry.mjs +59 -0
- package/package.json +6 -3
- package/scripts/build-client.mjs +2 -0
- package/scripts/setup.mjs +8 -7
- package/scripts/verify-crew-ui-polish.mjs +145 -0
- package/scripts/verify-history-ui.mjs +97 -0
- package/scripts/verify-npm-install.mjs +1 -1
- package/src/client/history-panel.tsx +108 -0
- package/src/client/host-readiness.mjs +15 -6
- package/src/client/index.tsx +86 -64
- package/src/client/model-callability-view.mjs +17 -0
- package/src/client/panel-chrome.tsx +40 -0
- package/src/client/quick-panel.tsx +88 -73
- package/src/client/readiness-envelope.mjs +34 -0
- package/src/config-readiness.mjs +181 -115
- package/src/extension-contract.mjs +44 -26
- package/src/history/admission-gate.mjs +51 -0
- package/src/history/archive-store.mjs +261 -0
- package/src/history/cleanup-plan.mjs +64 -0
- package/src/history/http.mjs +32 -0
- package/src/history/operation.mjs +86 -0
- package/src/history/runner.mjs +51 -0
- package/src/history/runtime.mjs +28 -0
- package/src/history/service.mjs +137 -0
- package/src/history/state.mjs +27 -0
- package/src/hub/index.mjs +141 -24
- package/src/hub-client.mjs +17 -15
- package/src/image-output.mjs +71 -0
- package/src/install/install-legacy.mjs +126 -23
- package/src/install/npx-lifecycle.mjs +373 -64
- package/src/install/official-frontend-assets.mjs +78 -0
- package/src/install/payload-content.mjs +88 -0
- package/src/install/windows-startup.mjs +107 -19
- package/src/install/windows-supervisor-adapter.mjs +443 -0
- package/src/install/windows-supervisor-lifecycle.mjs +782 -0
- package/src/install/zcode.mjs +53 -14
- package/src/job-contracts.mjs +37 -31
- package/src/jobs.mjs +2 -2
- package/src/mcp-runtime.mjs +74 -9
- package/src/model-callability-contract.mjs +79 -0
- package/src/multimodal.mjs +0 -0
- package/src/official-web-bridge.mjs +0 -2
- package/src/provider-health.mjs +130 -124
- package/src/provider-profile-store.mjs +12 -0
- package/src/provider-settings-store.mjs +347 -5
- package/src/readiness-matrix.mjs +20 -15
- package/src/runtime-identity-contract.mjs +34 -0
- package/src/runtime-identity.mjs +14 -4
- package/src/runtime-readiness-snapshot.mjs +285 -87
- package/src/server.mjs +86 -76
- package/src/standalone-sdk.mjs +23 -0
- package/src/supervisor/restart-request.mjs +64 -10
- package/src/workflow-runtime.mjs +28 -13
- package/windows/start-dsh-crew.cmd +57 -57
- package/windows/start-dsh-crew.ps1 +327 -55
- package/windows/supervisor-control.ps1 +467 -0
- package/zcode/AGENTS.md +4 -1
- package/zcode/agents/ds-reviewer.md +31 -36
- package/zcode/agents/ds-worker.md +31 -39
- package/zcode/commands/dsh-config.md +17 -6
- package/zcode/commands/dsh-status.md +5 -4
- package/.mcp.json +0 -8
package/src/client/index.tsx
CHANGED
|
@@ -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
|
-
|
|
49
|
+
openOfficial: '打开 3080 官方界面 →',
|
|
50
|
+
intro: '管理工作流、模型路由与宿主集成。展开分区查看和修改设置。',
|
|
51
|
+
surfaceLabel: '3210 / 高级配置',
|
|
47
52
|
integrations: '集成',
|
|
48
|
-
installed: '已安装', notInstalled: '未安装', ready: '
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
436
|
-
|
|
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'
|
|
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
|
-
? '
|
|
481
|
-
: 'This is the
|
|
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 ·
|
|
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 ? '
|
|
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 [
|
|
548
|
-
const
|
|
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
|
|
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),
|
|
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
|
-
|
|
639
|
-
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
652
|
-
|
|
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.
|
|
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
|
-
<
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
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
|
|
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(
|
|
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
|
+
}
|