@ran-sh/dsh-crew 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,136 +1,161 @@
1
- {
2
- "name": "@ran-sh/dsh-crew",
3
- "version": "0.4.2",
4
- "type": "module",
5
- "main": "./src/hub/entry.mjs",
6
- "bin": {
7
- "dsh-crew": "bin/dsh-crew.mjs"
8
- },
9
- "description": "DeepSeek Harness plugin: dispatch work to DSH agents from Claude Code / Codex, as native subagents with live progress",
10
- "dsh": {
11
- "bundle": {
12
- "patch": "./cordis.patch.yml"
13
- },
14
- "client": {
15
- "inject": [
16
- "@deepseek-ai/dsh-client-runtime",
17
- "@deepseek-ai/dsh-client-locale",
18
- "@deepseek-ai/dsh-client-ui-slots",
19
- "@deepseek-ai/dsh-client-ui-settings"
20
- ],
21
- "platform": "web"
22
- }
23
- },
24
- "scripts": {
25
- "mcp": "node src/server.mjs",
26
- "build:client": "tsdown src/client/entry.tsx --format cjs --platform browser --target es2022 --tsconfig tsconfig.client.json --deps.never-bundle react --deps.never-bundle react/jsx-runtime --deps.never-bundle react-dom --deps.never-bundle react-dom/client --out-dir .client-build --clean --logLevel warn && node scripts/build-client.mjs",
27
- "verify:npm-install": "node scripts/verify-npm-install.mjs",
28
- "verify:npm-install:official": "node scripts/verify-npm-install.mjs --with-official-dsh",
29
- "verify:official-bridge": "node scripts/verify-official-bridge-e2e.mjs"
30
- },
31
- "dependencies": {
32
- "@modelcontextprotocol/sdk": "^1.25.4",
33
- "zod": "^3.24.0"
34
- },
35
- "exports": {
36
- ".": "./src/hub/entry.mjs",
37
- "./client": "./lib/client.js",
38
- "./package.json": "./package.json"
39
- },
40
- "peerDependencies": {
41
- "react": "^18.0.0",
42
- "react-dom": "^18.0.0",
43
- "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
44
- "@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
45
- "@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
46
- "@deepseek-ai/dsh-bash-local": "0.1.1-rc.2",
47
- "@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
48
- "@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
49
- "@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
50
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
51
- "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
52
- "@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
53
- "@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
54
- "@deepseek-ai/dsh-sdk-client": "0.1.1-rc.2",
55
- "@deepseek-ai/dsh-sdk-jsonrpc-demo": "0.1.1-rc.2",
56
- "@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.1-rc.2",
57
- "@deepseek-ai/dsh-session": "0.1.1-rc.2",
58
- "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
59
- "@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
60
- "@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
61
- "@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
62
- "@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2"
63
- },
64
- "devDependencies": {
65
- "react": "^18.3.1",
66
- "react-dom": "^18.3.1",
67
- "tsdown": "^0.12.0",
68
- "typescript": "^5.6.0",
69
- "@types/react": "^18.3.0",
70
- "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
71
- "@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
72
- "@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
73
- "@deepseek-ai/dsh-bash-local": "0.1.1-rc.2",
74
- "@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
75
- "@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
76
- "@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
77
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
78
- "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
79
- "@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
80
- "@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
81
- "@deepseek-ai/dsh-sdk-client": "0.1.1-rc.2",
82
- "@deepseek-ai/dsh-sdk-jsonrpc-demo": "0.1.1-rc.2",
83
- "@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.1-rc.2",
84
- "@deepseek-ai/dsh-session": "0.1.1-rc.2",
85
- "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
86
- "@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
87
- "@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
88
- "@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
89
- "@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2"
90
- },
91
- "license": "MIT",
92
- "repository": {
93
- "type": "git",
94
- "url": "git+https://github.com/Ran-sh/dsh-crew.git"
95
- },
96
- "homepage": "https://github.com/Ran-sh/dsh-crew",
97
- "bugs": {
98
- "url": "https://github.com/Ran-sh/dsh-crew/issues"
99
- },
100
- "keywords": [
101
- "dsh",
102
- "deepseek",
103
- "claude-code",
104
- "codex",
105
- "subagent",
106
- "worker",
107
- "multimodal",
108
- "plugin",
109
- "agent"
110
- ],
111
- "files": [
112
- "bin",
113
- "lib",
114
- "src",
115
- "scripts",
116
- "agents",
117
- "commands",
118
- "codex",
119
- "statusline",
120
- "official-web-bridge",
121
- "cordis.patch.yml",
122
- "worker.cordis.yml",
123
- ".claude-plugin",
124
- ".mcp.json",
125
- "README.md",
126
- "README.*.md",
127
- "docs/job-contracts.md",
128
- "docs/readiness-matrix.md",
129
- "docs/gpt-relay-extension.md",
130
- "docs/images/dsh-crew-logo.png",
131
- "docs/images/dsh-crew-overview.png",
132
- "docs/images/dsh-crew-host.png",
133
- "docs/images/dsh-crew-jobs.png",
134
- "LICENSE"
135
- ]
136
- }
1
+ {
2
+ "name": "@ran-sh/dsh-crew",
3
+ "version": "0.5.1",
4
+ "type": "module",
5
+ "main": "./src/hub/entry.mjs",
6
+ "bin": {
7
+ "dsh-crew": "bin/dsh-crew.mjs"
8
+ },
9
+ "description": "DeepSeek Harness plugin: dispatch work to DSH agents from Claude Code / Codex, as native subagents with live progress",
10
+ "dsh": {
11
+ "bundle": {
12
+ "patch": "./cordis.patch.yml"
13
+ },
14
+ "client": {
15
+ "inject": [
16
+ "@deepseek-ai/dsh-client-runtime",
17
+ "@deepseek-ai/dsh-client-locale",
18
+ "@deepseek-ai/dsh-client-ui-slots",
19
+ "@deepseek-ai/dsh-client-ui-settings"
20
+ ],
21
+ "platform": "web"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "mcp": "node src/server.mjs",
26
+ "build:client": "tsdown src/client/entry.tsx --format cjs --platform browser --target es2022 --tsconfig tsconfig.client.json --deps.never-bundle react --deps.never-bundle react/jsx-runtime --deps.never-bundle react-dom --deps.never-bundle react-dom/client --out-dir .client-build --clean --logLevel warn && node scripts/build-client.mjs",
27
+ "verify:npm-install": "node scripts/verify-npm-install.mjs",
28
+ "verify:npm-install:official": "node scripts/verify-npm-install.mjs --with-official-dsh",
29
+ "verify:official-bridge": "node scripts/verify-official-bridge-e2e.mjs"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.25.4",
33
+ "zod": "^3.24.0"
34
+ },
35
+ "exports": {
36
+ ".": "./src/hub/entry.mjs",
37
+ "./client": "./lib/client.js",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "peerDependencies": {
41
+ "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
42
+ "@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
43
+ "@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
44
+ "@deepseek-ai/dsh-bash-local": "0.1.1-rc.2",
45
+ "@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
46
+ "@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
47
+ "@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
48
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
49
+ "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
50
+ "@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
51
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
52
+ "@deepseek-ai/dsh-sdk-client": "0.1.1-rc.2",
53
+ "@deepseek-ai/dsh-sdk-jsonrpc-demo": "0.1.1-rc.2",
54
+ "@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.1-rc.2",
55
+ "@deepseek-ai/dsh-session": "0.1.1-rc.2",
56
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
57
+ "@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
58
+ "@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
59
+ "@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
60
+ "@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2",
61
+ "react": "^18.0.0",
62
+ "react-dom": "^18.0.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@deepseek-ai/dsh-agent": { "optional": true },
66
+ "@deepseek-ai/dsh-agent-presets": { "optional": true },
67
+ "@deepseek-ai/dsh-agent-spine-demo": { "optional": true },
68
+ "@deepseek-ai/dsh-bash-local": { "optional": true },
69
+ "@deepseek-ai/dsh-compaction-basic": { "optional": true },
70
+ "@deepseek-ai/dsh-fs-local": { "optional": true },
71
+ "@deepseek-ai/dsh-fs-observation-policy": { "optional": true },
72
+ "@deepseek-ai/dsh-llm": { "optional": true },
73
+ "@deepseek-ai/dsh-llm-deepseek": { "optional": true },
74
+ "@deepseek-ai/dsh-sandbox-local": { "optional": true },
75
+ "@deepseek-ai/dsh-sandbox-policy": { "optional": true },
76
+ "@deepseek-ai/dsh-sdk-client": { "optional": true },
77
+ "@deepseek-ai/dsh-sdk-jsonrpc-demo": { "optional": true },
78
+ "@deepseek-ai/dsh-sdk-jsonrpc-server": { "optional": true },
79
+ "@deepseek-ai/dsh-session": { "optional": true },
80
+ "@deepseek-ai/dsh-session-persistence-jsonl": { "optional": true },
81
+ "@deepseek-ai/dsh-subprocess-local": { "optional": true },
82
+ "@deepseek-ai/dsh-token-meter": { "optional": true },
83
+ "@deepseek-ai/dsh-tool-fs": { "optional": true },
84
+ "@deepseek-ai/dsh-tool-todo": { "optional": true },
85
+ "react": { "optional": true },
86
+ "react-dom": { "optional": true }
87
+ },
88
+ "devDependencies": {
89
+ "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
90
+ "@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
91
+ "@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
92
+ "@deepseek-ai/dsh-bash-local": "0.1.1-rc.2",
93
+ "@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
94
+ "@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
95
+ "@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
96
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
97
+ "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
98
+ "@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
99
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
100
+ "@deepseek-ai/dsh-sdk-client": "0.1.1-rc.2",
101
+ "@deepseek-ai/dsh-sdk-jsonrpc-demo": "0.1.1-rc.2",
102
+ "@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.1-rc.2",
103
+ "@deepseek-ai/dsh-session": "0.1.1-rc.2",
104
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
105
+ "@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
106
+ "@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
107
+ "@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
108
+ "@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2",
109
+ "@types/react": "^18.3.0",
110
+ "react": "^18.3.1",
111
+ "react-dom": "^18.3.1",
112
+ "tsdown": "0.22.14",
113
+ "typescript": "^5.6.0"
114
+ },
115
+ "license": "MIT",
116
+ "repository": {
117
+ "type": "git",
118
+ "url": "git+https://github.com/Ran-sh/dsh-crew.git"
119
+ },
120
+ "homepage": "https://github.com/Ran-sh/dsh-crew",
121
+ "bugs": {
122
+ "url": "https://github.com/Ran-sh/dsh-crew/issues"
123
+ },
124
+ "keywords": [
125
+ "dsh",
126
+ "deepseek",
127
+ "claude-code",
128
+ "codex",
129
+ "subagent",
130
+ "worker",
131
+ "multimodal",
132
+ "plugin",
133
+ "agent"
134
+ ],
135
+ "files": [
136
+ "bin",
137
+ "lib",
138
+ "src",
139
+ "scripts",
140
+ "agents",
141
+ "commands",
142
+ "codex",
143
+ "statusline",
144
+ "official-web-bridge",
145
+ "cordis.patch.yml",
146
+ "worker.cordis.yml",
147
+ ".claude-plugin",
148
+ ".mcp.json",
149
+ "README.md",
150
+ "README.*.md",
151
+ "docs/job-contracts.md",
152
+ "docs/readiness-matrix.md",
153
+ "docs/gpt-relay-extension.md",
154
+ "docs/ui-surfaces.md",
155
+ "docs/images/dsh-crew-logo.png",
156
+ "docs/images/dsh-crew-overview.png",
157
+ "docs/images/dsh-crew-host.png",
158
+ "docs/images/dsh-crew-jobs.png",
159
+ "LICENSE"
160
+ ]
161
+ }
@@ -9,13 +9,13 @@
9
9
  // or fully resolving the very large official DSH dependency graph.
10
10
 
11
11
  import { mkdtemp, readFile, rm } from 'node:fs/promises';
12
+ import { existsSync } from 'node:fs';
12
13
  import os from 'node:os';
13
14
  import path from 'node:path';
14
15
  import { spawnSync } from 'node:child_process';
15
16
  import { fileURLToPath } from 'node:url';
16
17
 
17
18
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
18
- const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
19
19
  const registry = process.env.NPM_REGISTRY ?? 'https://registry.npmjs.org/';
20
20
  export const supportedDshVersion = '0.1.1-rc.2';
21
21
  const verifyOfficialDsh = process.argv.includes('--with-official-dsh');
@@ -23,6 +23,19 @@ const DSH_PACKAGE = '@deepseek-ai/dsh';
23
23
  const DSH_PREFIX = '@deepseek-ai/dsh-';
24
24
  const MAX_DIRECT_PEERS = 64;
25
25
 
26
+ export function resolveNpmInvocation({
27
+ platform = process.platform,
28
+ nodePath = process.execPath,
29
+ fileExists = existsSync,
30
+ } = {}) {
31
+ if (platform !== 'win32') return { command: 'npm', argsPrefix: [] };
32
+ const npmCli = path.join(path.dirname(nodePath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
33
+ if (!fileExists(npmCli)) throw new Error(`npm CLI was not found beside Node.js: ${npmCli}`);
34
+ return { command: nodePath, argsPrefix: [npmCli] };
35
+ }
36
+
37
+ const npmInvocation = resolveNpmInvocation();
38
+
26
39
  /**
27
40
  * The candidate version is derived from the candidate package manifest at the
28
41
  * checkout root instead of a hard-coded release literal, so the verifier never
@@ -217,10 +230,10 @@ export async function auditOfficialDshCohort({
217
230
  }
218
231
 
219
232
  function run(args, cwd = root) {
220
- const result = spawnSync(npmCommand, args, {
233
+ const result = spawnSync(npmInvocation.command, [...npmInvocation.argsPrefix, ...args], {
221
234
  cwd,
222
235
  encoding: 'utf8',
223
- shell: process.platform === 'win32',
236
+ shell: false,
224
237
  windowsHide: true,
225
238
  });
226
239
  if (result.error) throw result.error;
@@ -4,9 +4,10 @@ export const SETTINGS_SECTION_IDS = Object.freeze([
4
4
  'integrations',
5
5
  'workflow',
6
6
  'flash',
7
- 'pro',
8
- 'dispatch',
9
- 'runtime',
7
+ 'pro',
8
+ 'dispatch',
9
+ 'adaptive',
10
+ 'runtime',
10
11
  'multimodal',
11
12
  'providers',
12
13
  'jobs',
@@ -1,236 +1,9 @@
1
- import React, { useEffect, useState, useSyncExternalStore } from 'react';
2
- import { apply as applyCrew, inject as crewInject } from './index';
3
- import { ActivationSummary } from './activation-summary';
4
-
5
- export const inject = crewInject;
6
- const API = '/_dsh/dsh-crew';
7
-
8
- type AdaptiveConfig = {
9
- enabled: boolean;
10
- window_size: number;
11
- min_samples: number;
12
- };
13
-
14
- const DEFAULT_ADAPTIVE: AdaptiveConfig = { enabled: false, window_size: 8, min_samples: 2 };
15
-
16
- function clampInt(value: unknown, fallback: number, min: number, max: number) {
17
- const parsed = Number(value);
18
- if (!Number.isInteger(parsed)) return fallback;
19
- return Math.max(min, Math.min(max, parsed));
20
- }
21
-
22
- function normalizeAdaptive(value: any): AdaptiveConfig {
23
- const windowSize = clampInt(value?.window_size, DEFAULT_ADAPTIVE.window_size, 1, 32);
24
- return {
25
- enabled: value?.enabled === true,
26
- window_size: windowSize,
27
- min_samples: clampInt(value?.min_samples, DEFAULT_ADAPTIVE.min_samples, 1, windowSize),
28
- };
29
- }
30
-
31
- function useLocale(ctx: any) {
32
- return useSyncExternalStore(
33
- (notify: () => void) => ctx.on('locale/change', notify),
34
- () => ctx.locale.getLocale().active,
35
- () => ctx.locale.getLocale().active,
36
- );
37
- }
38
-
39
- function AdaptiveRoutingPanel({ ctx }: { ctx: any }) {
40
- const locale = useLocale(ctx);
41
- const zh = locale === 'zh';
42
- const [adaptive, setAdaptive] = useState<AdaptiveConfig>(DEFAULT_ADAPTIVE);
43
- const [loaded, setLoaded] = useState(false);
44
- const [saving, setSaving] = useState(false);
45
- const [message, setMessage] = useState('');
46
-
47
- const copy = zh ? {
48
- title: '自适应模型路由(实验)',
49
- hint: '默认关闭。仅对系统自动产生的候选做健康排序;显式 Provider / Model 优先级永远保持原序。信号只来自本进程内 Crew 已观察到的成功、失败、超时与粗粒度延迟,不读取额度、价格或凭据。重启 Hub 会清空健康历史。',
50
- enabled: '启用自适应路由',
51
- window: '健康窗口',
52
- minSamples: '最少样本',
53
- windowHint: '每个 role/provider/model 最多参考最近 1–32 次结果。',
54
- minHint: '达到该样本数后才允许健康分数影响自动候选顺序。',
55
- boundary: '生效边界:下一工作流',
56
- saved: '已保存',
57
- loading: '加载中…',
58
- } : {
59
- title: 'Adaptive Model Routing (experimental)',
60
- hint: 'Off by default. Health ordering applies only to automatically derived candidates; explicit Provider / Model priorities always keep their order. Signals are limited to Crew-observed success, failure, timeout, and coarse latency in this process—never quota, pricing, or credentials. Restarting the Hub clears the history.',
61
- enabled: 'Enable adaptive routing',
62
- window: 'Health window',
63
- minSamples: 'Minimum samples',
64
- windowHint: 'Use at most the most recent 1–32 outcomes per role/provider/model.',
65
- minHint: 'Health may affect automatic candidate order only after this many samples.',
66
- boundary: 'Activation boundary: next workflow',
67
- saved: 'Saved',
68
- loading: 'Loading…',
69
- };
70
-
71
- useEffect(() => {
72
- let cancelled = false;
73
- const load = async () => {
74
- try {
75
- const lang = zh ? 'zh' : 'en';
76
- const res = await fetch(`${API}/config?lang=${lang}`, { cache: 'no-store' });
77
- const body = await res.json();
78
- if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
79
- if (!cancelled) {
80
- setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
81
- setMessage('');
82
- setLoaded(true);
83
- }
84
- } catch (err: any) {
85
- if (!cancelled) {
86
- setMessage(err?.message ?? String(err));
87
- setLoaded(true);
88
- }
89
- }
90
- };
91
- void load();
92
- return () => { cancelled = true; };
93
- }, [zh]);
94
-
95
- const save = async (candidate: AdaptiveConfig) => {
96
- const next = normalizeAdaptive(candidate);
97
- setAdaptive(next);
98
- setSaving(true);
99
- try {
100
- const lang = zh ? 'zh' : 'en';
101
- const res = await fetch(`${API}/config?lang=${lang}`, {
102
- method: 'POST',
103
- headers: { 'content-type': 'application/json' },
104
- body: JSON.stringify({ worker: { model_policy: { adaptive: next } } }),
105
- });
106
- const body = await res.json();
107
- if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
108
- setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
109
- setMessage(copy.saved);
110
- setTimeout(() => setMessage(''), 1500);
111
- } catch (err: any) {
112
- setMessage(err?.message ?? String(err));
113
- } finally {
114
- setSaving(false);
115
- }
116
- };
117
-
118
- if (!loaded) return <div style={{ fontSize: 12, opacity: 0.6 }}>{copy.loading}</div>;
119
-
120
- return (
121
- <div style={{ display: 'flex', flexDirection: 'column', gap: 10, fontSize: 13, lineHeight: 1.55 }}>
122
- <div>
123
- <div style={{ fontWeight: 600, fontSize: 14 }}>{copy.title}</div>
124
- <div style={{ opacity: 0.68, fontSize: 12, marginTop: 2 }}>{copy.hint}</div>
125
- </div>
126
- <label style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
127
- <input
128
- type="checkbox"
129
- checked={adaptive.enabled}
130
- disabled={saving}
131
- onChange={(event) => { void save({ ...adaptive, enabled: event.target.checked }); }}
132
- />
133
- <span>{copy.enabled}</span>
134
- </label>
135
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }}>
136
- <label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
137
- <span style={{ fontSize: 12, fontWeight: 500 }}>{copy.window}</span>
138
- <input
139
- type="number"
140
- min={1}
141
- max={32}
142
- value={adaptive.window_size}
143
- disabled={saving}
144
- onChange={(event) => {
145
- const windowSize = clampInt(event.target.value, adaptive.window_size, 1, 32);
146
- setAdaptive((current) => ({
147
- ...current,
148
- window_size: windowSize,
149
- min_samples: Math.min(current.min_samples, windowSize),
150
- }));
151
- }}
152
- onBlur={() => { void save(adaptive); }}
153
- style={{ padding: '5px 7px', borderRadius: 5, border: '1px solid rgba(128,128,128,0.35)', background: 'transparent', color: 'inherit' }}
154
- />
155
- <span style={{ fontSize: 10.5, opacity: 0.55 }}>{copy.windowHint}</span>
156
- </label>
157
- <label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
158
- <span style={{ fontSize: 12, fontWeight: 500 }}>{copy.minSamples}</span>
159
- <input
160
- type="number"
161
- min={1}
162
- max={adaptive.window_size}
163
- value={adaptive.min_samples}
164
- disabled={saving}
165
- onChange={(event) => setAdaptive((current) => ({
166
- ...current,
167
- min_samples: clampInt(event.target.value, current.min_samples, 1, current.window_size),
168
- }))}
169
- onBlur={() => { void save(adaptive); }}
170
- style={{ padding: '5px 7px', borderRadius: 5, border: '1px solid rgba(128,128,128,0.35)', background: 'transparent', color: 'inherit' }}
171
- />
172
- <span style={{ fontSize: 10.5, opacity: 0.55 }}>{copy.minHint}</span>
173
- </label>
174
- </div>
175
- <div style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 11.5, opacity: 0.62 }}>
176
- <span>{copy.boundary}</span>
177
- {message && <span>· {message}</span>}
178
- </div>
179
- </div>
180
- );
181
- }
182
-
183
- function ActivationBoundaryPanel({ ctx }: { ctx: any }) {
184
- const locale = useLocale(ctx);
185
- const [activation, setActivation] = useState<any>(null);
186
- const [error, setError] = useState('');
187
-
188
- useEffect(() => {
189
- let cancelled = false;
190
- const load = async () => {
191
- try {
192
- const lang = locale === 'zh' ? 'zh' : 'en';
193
- const res = await fetch(`${API}/config?lang=${lang}`, { cache: 'no-store' });
194
- const body = await res.json();
195
- if (!cancelled) {
196
- if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
197
- setActivation(body?.config?.config_activation ?? null);
198
- setError('');
199
- }
200
- } catch (err: any) {
201
- if (!cancelled) setError(err?.message ?? String(err));
202
- }
203
- };
204
- void load();
205
- return () => { cancelled = true; };
206
- }, [locale]);
207
-
208
- return (
209
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8, fontSize: 13, lineHeight: 1.55 }}>
210
- {error && <div style={{ fontSize: 11.5, opacity: 0.6 }}>{error}</div>}
211
- <ActivationSummary activation={activation ?? undefined} locale={locale} />
212
- </div>
213
- );
214
- }
215
-
216
- export function apply(ctx: any): void {
217
- applyCrew(ctx);
218
- ctx.slots.inject('settings.section', () => ctx.slots.register(
219
- {
220
- name: 'settings.section',
221
- id: 'dsh-crew-adaptive-routing',
222
- order: 65,
223
- label: () => ctx.locale.getLocale().active === 'zh' ? 'DSH Crew · 自适应路由' : 'DSH Crew · Adaptive Routing',
224
- },
225
- () => <AdaptiveRoutingPanel ctx={ctx} />,
226
- ));
227
- ctx.slots.inject('settings.section', () => ctx.slots.register(
228
- {
229
- name: 'settings.section',
230
- id: 'dsh-crew-runtime-controls',
231
- order: 66,
232
- label: () => ctx.locale.getLocale().active === 'zh' ? 'DSH Crew · 生效边界' : 'DSH Crew · Activation',
233
- },
234
- () => <ActivationBoundaryPanel ctx={ctx} />,
235
- ));
236
- }
1
+ import { apply as applyCrew, inject as crewInject } from './index';
2
+
3
+ export const inject = crewInject;
4
+
5
+ // Keep a single settings slot. Adaptive routing and activation boundaries live
6
+ // inside the main DSH Crew operations console instead of creating sidebar noise.
7
+ export function apply(ctx: any): void {
8
+ applyCrew(ctx);
9
+ }
@@ -0,0 +1,45 @@
1
+ export const READINESS_STATES = Object.freeze({
2
+ READY: 'READY',
3
+ DEGRADED: 'DEGRADED',
4
+ UNAVAILABLE: 'UNAVAILABLE',
5
+ UNKNOWN: 'UNKNOWN',
6
+ });
7
+
8
+ function componentState(status, keys, { aligned = false } = {}) {
9
+ if (status === undefined || status === null || typeof status !== 'object') return READINESS_STATES.UNKNOWN;
10
+ if (status.installed === false) return READINESS_STATES.UNAVAILABLE;
11
+ if (status.installed !== true || !status.components || typeof status.components !== 'object') return READINESS_STATES.UNKNOWN;
12
+ const evidence = keys.map((key) => status.components[key]);
13
+ if (evidence.some((value) => typeof value !== 'boolean')) return READINESS_STATES.UNKNOWN;
14
+ const ready = evidence.every(Boolean) && (!aligned || status.components.target_alignment === true);
15
+ return ready ? READINESS_STATES.READY : READINESS_STATES.DEGRADED;
16
+ }
17
+
18
+ function runtimeState(runtime) {
19
+ if (runtime === undefined) return READINESS_STATES.UNKNOWN;
20
+ if (runtime === null) return READINESS_STATES.UNAVAILABLE;
21
+ return runtime?.ok === true && runtime.service === 'dsh-crew-hub'
22
+ ? READINESS_STATES.READY
23
+ : READINESS_STATES.DEGRADED;
24
+ }
25
+
26
+ function bridgeState(surface) {
27
+ if (surface === 'official-bridge') return READINESS_STATES.READY;
28
+ if (surface === 'native-crew-harness') return READINESS_STATES.UNAVAILABLE;
29
+ return READINESS_STATES.UNKNOWN;
30
+ }
31
+
32
+ /** Project only structured, non-secret installer/runtime evidence. */
33
+ export function projectHostReadiness({ installStatus, runtime, surface } = {}) {
34
+ const codex = installStatus?.codex;
35
+ const claude = installStatus?.claude;
36
+ return [
37
+ { id: 'codex_mcp', state: componentState(codex, ['mcp'], { aligned: true }) },
38
+ { id: 'ds_worker', state: componentState(codex, ['worker_role'], { aligned: true }) },
39
+ { id: 'ds_reviewer', state: componentState(codex, ['reviewer_role'], { aligned: true }) },
40
+ { id: 'claude_plugin', state: componentState(claude, ['enabled', 'marketplace', 'snapshot', 'permissions']) },
41
+ { id: 'crew_harness', state: runtimeState(runtime), detail: runtime?.runtime_version ?? null },
42
+ { id: 'official_bridge', state: bridgeState(surface) },
43
+ ];
44
+ }
45
+