@ran-sh/dsh-crew 1.8.0 → 1.9.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.
Files changed (151) hide show
  1. package/.claude-plugin/marketplace.json +16 -16
  2. package/.claude-plugin/plugin.json +14 -14
  3. package/LICENSE +21 -21
  4. package/README.de.md +359 -359
  5. package/README.es.md +359 -359
  6. package/README.fr.md +359 -359
  7. package/README.hi.md +359 -359
  8. package/README.id.md +359 -359
  9. package/README.ja.md +359 -359
  10. package/README.ko.md +359 -359
  11. package/README.md +126 -126
  12. package/README.pt.md +359 -359
  13. package/README.ru.md +359 -359
  14. package/README.th.md +359 -359
  15. package/README.tr.md +359 -359
  16. package/README.vi.md +359 -359
  17. package/README.zh-TW.md +359 -359
  18. package/README.zh.md +116 -116
  19. package/agents/ds-flash.md +24 -24
  20. package/agents/ds-pro.md +24 -24
  21. package/agents/ds-reviewer.md +23 -23
  22. package/agents/ds-worker.md +23 -23
  23. package/bin/dsh-crew.mjs +16 -16
  24. package/codex/agents/ds-flash.toml +34 -34
  25. package/codex/agents/ds-pro.toml +34 -34
  26. package/codex/agents/ds-reviewer.toml +32 -32
  27. package/codex/agents/ds-worker.toml +32 -32
  28. package/codex/prompts/dsh-config.md +19 -19
  29. package/codex/prompts/dsh-status.md +5 -5
  30. package/commands/config.md +23 -23
  31. package/commands/off.md +5 -5
  32. package/commands/on.md +5 -5
  33. package/commands/status.md +9 -9
  34. package/cordis.patch.yml +4 -4
  35. package/docs/gpt-relay-extension.md +103 -103
  36. package/docs/installation.md +138 -138
  37. package/docs/job-contracts.md +103 -103
  38. package/docs/readiness-matrix.md +85 -85
  39. package/docs/ui-surfaces.md +107 -107
  40. package/official-web-bridge/cordis.patch.yml +4 -4
  41. package/official-web-bridge/entry.mjs +1 -1
  42. package/official-web-bridge/overlay-entry.mjs +59 -59
  43. package/official-web-bridge/package.json +25 -25
  44. package/package.json +1 -1
  45. package/scripts/build-client.mjs +49 -49
  46. package/scripts/live-crew-smoke.mjs +39 -39
  47. package/scripts/live-policy-matrix.mjs +177 -177
  48. package/scripts/policy-probe.mjs +101 -101
  49. package/scripts/remove-legacy-official-bridge.ps1 +89 -89
  50. package/scripts/setup.mjs +393 -393
  51. package/scripts/smoke-real.mjs +110 -110
  52. package/scripts/smoke.mjs +78 -78
  53. package/scripts/verify-crew-ui-polish.mjs +145 -145
  54. package/scripts/verify-history-ui.mjs +97 -97
  55. package/scripts/verify-installer-fix.mjs +26 -26
  56. package/scripts/verify-npm-install.mjs +311 -311
  57. package/scripts/verify-official-bridge-e2e.mjs +192 -192
  58. package/src/adaptive-routing.mjs +260 -260
  59. package/src/client/activation-summary.tsx +64 -64
  60. package/src/client/collapsible-sections.mjs +55 -55
  61. package/src/client/history-panel.tsx +108 -108
  62. package/src/client/host-readiness.mjs +71 -71
  63. package/src/client/index.tsx +1711 -1711
  64. package/src/client/model-callability-view.mjs +17 -17
  65. package/src/client/panel-chrome.tsx +40 -40
  66. package/src/client/quick-entry.tsx +10 -10
  67. package/src/client/quick-panel.tsx +275 -275
  68. package/src/client/readiness-envelope.mjs +34 -34
  69. package/src/client/surface-detection.mjs +43 -43
  70. package/src/config-readiness.mjs +226 -226
  71. package/src/credential-reference.mjs +38 -38
  72. package/src/delivery.mjs +205 -205
  73. package/src/dsh-cli-runtime.mjs +1021 -1021
  74. package/src/dsh-cohort.mjs +20 -20
  75. package/src/extension-contract.mjs +104 -104
  76. package/src/failure-classification.mjs +201 -201
  77. package/src/history/admission-gate.mjs +67 -67
  78. package/src/history/archive-store.mjs +272 -272
  79. package/src/history/cleanup-plan.mjs +89 -89
  80. package/src/history/http.mjs +32 -32
  81. package/src/history/operation.mjs +86 -86
  82. package/src/history/runner-detach.mjs +34 -34
  83. package/src/history/runner.mjs +52 -52
  84. package/src/history/runtime.mjs +36 -36
  85. package/src/history/service.mjs +177 -177
  86. package/src/history/state.mjs +27 -27
  87. package/src/hub/entry.mjs +104 -104
  88. package/src/hub/index.mjs +2694 -2694
  89. package/src/hub-client.mjs +154 -154
  90. package/src/hub-compatibility.mjs +42 -42
  91. package/src/i18n.mjs +19 -19
  92. package/src/information-flow.mjs +67 -67
  93. package/src/install/cli.mjs +28 -28
  94. package/src/install/install-legacy.mjs +711 -711
  95. package/src/install/install.mjs +483 -483
  96. package/src/install/npx-lifecycle.mjs +3456 -3456
  97. package/src/install/official-frontend-assets.mjs +78 -78
  98. package/src/install/official-web.mjs +95 -95
  99. package/src/install/payload-content.mjs +88 -88
  100. package/src/install/windows-startup.mjs +236 -236
  101. package/src/install/windows-supervisor-adapter.mjs +443 -443
  102. package/src/install/windows-supervisor-lifecycle.mjs +782 -782
  103. package/src/install/zcode.mjs +397 -397
  104. package/src/job-contracts.mjs +255 -255
  105. package/src/local-request-guard.mjs +60 -60
  106. package/src/mcp-runtime.mjs +340 -332
  107. package/src/model-callability-contract.mjs +79 -79
  108. package/src/model-catalog.mjs +180 -180
  109. package/src/model-routing.mjs +586 -586
  110. package/src/model-schedule.mjs +207 -207
  111. package/src/official-web-bridge.mjs +447 -447
  112. package/src/policy.mjs +235 -235
  113. package/src/provider-delete-adapters.mjs +1934 -1934
  114. package/src/provider-health.mjs +130 -130
  115. package/src/provider-inventory.mjs +182 -182
  116. package/src/provider-layer-migration-adapters.mjs +759 -759
  117. package/src/provider-layer-migration.mjs +198 -198
  118. package/src/provider-lifecycle-state.mjs +103 -103
  119. package/src/provider-lifecycle.mjs +252 -252
  120. package/src/provider-profile-store.mjs +390 -390
  121. package/src/provider-settings-store.mjs +633 -633
  122. package/src/provider-store-lock.mjs +67 -67
  123. package/src/readiness-matrix.mjs +181 -181
  124. package/src/removable-waiter.mjs +29 -29
  125. package/src/role-profiles.mjs +107 -107
  126. package/src/runtime-controls.mjs +84 -84
  127. package/src/runtime-identity-contract.mjs +34 -34
  128. package/src/runtime-identity.mjs +235 -235
  129. package/src/runtime-readiness-snapshot.mjs +285 -285
  130. package/src/server.mjs +581 -581
  131. package/src/session-origins.mjs +60 -60
  132. package/src/standalone-sdk.mjs +23 -23
  133. package/src/status-shard.mjs +63 -63
  134. package/src/structured-error-code.mjs +38 -38
  135. package/src/supervisor/restart-request.mjs +256 -256
  136. package/src/workflow-runtime.mjs +739 -739
  137. package/src/workflow.mjs +155 -155
  138. package/src/workspace-audit.mjs +231 -231
  139. package/src/workspace-context.mjs +146 -146
  140. package/src/workspace-isolation.mjs +455 -401
  141. package/src/workspace-readiness.mjs +32 -32
  142. package/statusline/statusline.sh +14 -14
  143. package/statusline/worker-segment.sh +35 -35
  144. package/windows/start-dsh-crew.cmd +57 -57
  145. package/windows/start-dsh-crew.ps1 +1302 -1302
  146. package/windows/supervisor-control.ps1 +467 -467
  147. package/worker.cordis.yml +67 -67
  148. package/zcode/agents/ds-reviewer.md +31 -31
  149. package/zcode/agents/ds-worker.md +31 -31
  150. package/zcode/commands/dsh-config.md +17 -17
  151. package/zcode/commands/dsh-status.md +5 -5
@@ -1,275 +1,275 @@
1
- import { useCallback, useEffect, useState } from 'react';
2
- import { PanelHeader, PanelStyles } from './panel-chrome';
3
-
4
- // DSH Crew QUICK CONTROLS panel for the official 3080 surface.
5
- //
6
- // Narrow by design: only the user-facing master switch, flash/pro model
7
- // priority lists — all other operations belong
8
- // to the native 3210 full control plane. Talks only to the quick endpoints
9
- // (/_dsh/dsh-crew/quick-config, /quick-status). Runtime maintenance stays on 3210.
10
-
11
- const API = '/_dsh/dsh-crew';
12
- const CREW_CONTROL_PLANE_URL = 'http://127.0.0.1:3210/';
13
- const FULL = 'http://127.0.0.1:3210/';
14
-
15
- type ModelEntry = { provider: string; model: string };
16
- type QuickConfig = {
17
- subagents_enabled?: boolean;
18
- flash_model_priority?: ModelEntry[];
19
- pro_model_priority?: ModelEntry[];
20
- };
21
-
22
- const RESTART_KEYS = new Set([
23
- ]);
24
-
25
- const T = {
26
- zh: {
27
- title: 'DSH Crew 快捷控制',
28
- openFull: '打开 3210 后台 →',
29
- running: '后台已连接',
30
- surface: '3080 / 日常控制',
31
- description: '常用开关与模型顺序在这里调整,更多设置进入后台。',
32
- priorityHint: '从上到下按优先级排列;从列表移除不会删除 Provider。',
33
- empty: '未配置优先模型,按现有后备策略选择。',
34
- models: (n: number) => `${n} 个模型`,
35
- moveUp: '上移', moveDown: '下移', remove: '从优先级移除',
36
- unavailable: 'Crew 后端不可用',
37
- openDiag: '打开诊断',
38
- crew: 'Crew',
39
- enabled: '启用子 Agent',
40
- flash: 'Worker / Flash',
41
- pro: 'Reviewer / Pro',
42
- addModel: '+ 添加模型',
43
- provider: 'Provider',
44
- applyRestart: '应用并重启 Crew',
45
- savedNeedsRestart: '配置已保存 · 需要重启 Crew 才会生效',
46
- saved: '已保存',
47
- working: '处理中…',
48
- providerPlaceholder: 'provider',
49
- modelPlaceholder: 'model',
50
- },
51
- en: {
52
- title: 'DSH Crew Quick Controls',
53
- openFull: 'Open Crew backend (3210) →',
54
- running: 'Backend connected',
55
- surface: '3080 / DAILY CONTROLS',
56
- description: 'Manage everyday switches and model order here. Open the backend for advanced settings.',
57
- priorityHint: 'Ordered by priority. Removing a model here does not delete its provider.',
58
- empty: 'No priority models. The existing fallback policy applies.',
59
- models: (n: number) => `${n} models`,
60
- moveUp: 'Move up', moveDown: 'Move down', remove: 'Remove from priority',
61
- unavailable: 'Crew backend unavailable',
62
- openDiag: 'Open diagnostics',
63
- crew: 'Crew',
64
- enabled: 'Enable sub-agents',
65
- flash: 'Worker / Flash',
66
- pro: 'Reviewer / Pro',
67
- addModel: '+ Add model',
68
- provider: 'Provider',
69
- applyRestart: 'Apply & restart Crew',
70
- savedNeedsRestart: 'Saved · restart Crew to take effect',
71
- saved: 'Saved',
72
- working: 'Working…',
73
- providerPlaceholder: 'provider',
74
- modelPlaceholder: 'Model',
75
- },
76
- };
77
-
78
- function LocalStyles() {
79
- return (
80
- <style>{`
81
- .dsh-crew-ui.crew-quick-card { display: flex; flex-direction: column; gap: 12px; }
82
- .crew-quick-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
83
- .crew-quick-title { font-size: 16px; font-weight: 680; }
84
- .crew-quick-section { font-weight: 650; opacity: 0.85; }
85
- .crew-quick-chip { border: 1px solid rgba(128,128,128,0.35); border-radius: 999px; padding: 1px 10px; font-size: 12px; }
86
- .crew-quick-card .crew-quick-btn { border: 1px solid var(--crew-line); border-radius: 7px; min-height: 30px; padding: 3px 9px; cursor: pointer; background: transparent; color: inherit; font-size: 12px; }
87
- .crew-quick-btn.primary { border-color: #4a9eff; color: #4a9eff; font-weight: 650; }
88
- .crew-quick-card .crew-quick-input { border: 1px solid var(--crew-line); border-radius: 6px; padding: 6px 9px; font-size: 12px; min-width: 0; width: 100%; background: transparent; color: inherit; }
89
- .crew-quick-notice { opacity: 0.75; font-size: 12.5px; }
90
- .crew-quick-card .crew-quick-master { padding: 11px 14px; border: 1px solid var(--crew-line); border-radius: 10px; justify-content: space-between; }
91
- .crew-quick-card input[type=checkbox] { accent-color: var(--crew-accent); width: 15px; height: 15px; }
92
- .crew-quick-card label { display: inline-flex; gap: 7px; align-items: center; }
93
- .crew-quick-card .crew-quick-group { border: 1px solid var(--crew-line); border-radius: 10px; overflow: hidden; }
94
- .crew-quick-card .crew-quick-group > summary { cursor: pointer; padding: 12px 14px; }
95
- .crew-quick-card .crew-quick-count { margin-left: 10px; font-size: 11px; opacity: .65; font-weight: 400; }
96
- .crew-quick-card .crew-quick-content { padding: 0 14px 14px; }
97
- .crew-quick-card .crew-quick-list { padding: 0; margin: 10px 0; list-style: none; }
98
- .crew-quick-card .crew-quick-model { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: 9px; align-items: center; border-top: 1px solid var(--crew-line); padding: 10px 0; }
99
- .crew-quick-card .crew-quick-rank { opacity: .5; font-size: 11px; font-variant-numeric: tabular-nums; }
100
- .crew-quick-card .crew-quick-model-name { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
101
- .crew-quick-card .crew-quick-provider { font-size: 11px; opacity: .65; overflow-wrap: anywhere; }
102
- .crew-quick-card .crew-quick-actions { display: flex; gap: 4px; }
103
- .crew-quick-card .crew-quick-add > summary { color: var(--crew-accent); cursor: pointer; font-size: 12px; padding: 4px 0; }
104
- .crew-quick-card .crew-quick-form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: 7px; margin-top: 9px; }
105
- .crew-quick-card .crew-quick-media { display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 10px; align-items: center; margin-top: 10px; }
106
- @media (max-width: 640px) { .crew-quick-card .crew-quick-form { grid-template-columns: minmax(0, 1fr); } .crew-quick-card .crew-quick-model { grid-template-columns: 18px minmax(0, 1fr); } .crew-quick-card .crew-quick-actions { grid-column: 2; justify-content: flex-end; } }
107
- `}</style>
108
- );
109
- }
110
-
111
- async function readJson(res: Response) {
112
- if (!res.ok) return { ok: false, status: res.status };
113
- try { return await res.json(); } catch { return { ok: false }; }
114
- }
115
-
116
- export function QuickPanel({ ctx }: { ctx: any }) {
117
- const locale = ctx?.locale?.getLocale?.().active === 'zh' ? 'zh' : 'en';
118
- const t = (T as any)[locale] ?? T.zh;
119
- const [config, setConfig] = useState<QuickConfig | null>(null);
120
- const [ready, setReady] = useState<boolean | null>(null);
121
- const [notice, setNotice] = useState('');
122
- const [busy, setBusy] = useState(false);
123
- const [restartPending, setRestartPending] = useState(false);
124
- const [drafts, setDrafts] = useState<Record<string, ModelEntry>>({});
125
-
126
- const load = useCallback(async () => {
127
- try {
128
- const status = await readJson(await fetch(`${API}/quick-status`, { cache: 'no-store' }));
129
- if (!status.ok) { setReady(false); return; }
130
- setReady(true);
131
- setConfig(status.config ?? {});
132
- } catch { setReady(false); }
133
- }, []);
134
-
135
- useEffect(() => { void load(); }, [load]);
136
-
137
- const patch = useCallback(async (next: QuickConfig) => {
138
- setBusy(true); setNotice(t.working);
139
- try {
140
- const res = await readJson(await fetch(`${API}/quick-config`, {
141
- method: 'POST', headers: { 'content-type': 'application/json' },
142
- body: JSON.stringify(next),
143
- }));
144
- if (!res.ok) throw new Error(res.code ?? res.error ?? 'save failed');
145
- setConfig(res.config ?? {});
146
- const needsRestart = Object.keys(next).some((k) => RESTART_KEYS.has(k));
147
- if (needsRestart) { setRestartPending(true); setNotice(t.savedNeedsRestart); }
148
- else { setNotice(t.saved); setTimeout(() => setNotice(''), 1500); }
149
- return true;
150
- } catch (e: any) {
151
- setNotice(String(e?.message ?? e));
152
- return false;
153
- } finally { setBusy(false); }
154
- }, [t]);
155
-
156
- const toggle = (key: 'subagents_enabled', value: boolean) => {
157
- setConfig((c) => ({ ...(c ?? {}), [key]: value }));
158
- void patch({ [key]: value } as QuickConfig);
159
- };
160
-
161
-
162
- const moveModel = (listKey: 'flash_model_priority' | 'pro_model_priority', index: number, dir: -1 | 1) => {
163
- const list = [...((config?.[listKey] as ModelEntry[] | undefined) ?? [])];
164
- const to = index + dir;
165
- if (to < 0 || to >= list.length) return;
166
- [list[index], list[to]] = [list[to], list[index]];
167
- setConfig((c) => ({ ...(c ?? {}), [listKey]: list }));
168
- void patch({ [listKey]: list } as unknown as QuickConfig);
169
- };
170
-
171
- const removeModel = (listKey: 'flash_model_priority' | 'pro_model_priority', index: number) => {
172
- const list = [...((config?.[listKey] as ModelEntry[] | undefined) ?? [])];
173
- list.splice(index, 1);
174
- setConfig((c) => ({ ...(c ?? {}), [listKey]: list }));
175
- void patch({ [listKey]: list } as unknown as QuickConfig);
176
- };
177
-
178
- const addModel = (listKey: 'flash_model_priority' | 'pro_model_priority') => {
179
- const draft = drafts[listKey] ?? { provider: '', model: '' };
180
- if (!draft.provider.trim() || !draft.model.trim()) return;
181
- const list = [...((config?.[listKey] as ModelEntry[] | undefined) ?? []), { provider: draft.provider.trim(), model: draft.model.trim() }];
182
- setConfig((c) => ({ ...(c ?? {}), [listKey]: list }));
183
- setDrafts((d) => ({ ...d, [listKey]: { provider: '', model: '' } }));
184
- void patch({ [listKey]: list } as unknown as QuickConfig);
185
- };
186
-
187
- const modelList = (listKey: 'flash_model_priority' | 'pro_model_priority', label: string) => {
188
- const list: ModelEntry[] = (config?.[listKey] as ModelEntry[] | undefined) ?? [];
189
- const draft = drafts[listKey] ?? { provider: '', model: '' };
190
- return (
191
- <details className="crew-quick-group" open={listKey === 'flash_model_priority'}>
192
- <summary><strong>{label}</strong><span className="crew-quick-count">{t.models(list.length)}</span></summary>
193
- <div className="crew-quick-content">
194
- <div className="crew-quick-notice">{t.priorityHint}</div>
195
- {list.length === 0 && <p className="crew-quick-notice">{t.empty}</p>}
196
- <ol className="crew-quick-list" aria-label={label}>
197
- {list.map((entry, i) => (
198
- <li key={`${entry.provider}/${entry.model}/${i}`} className="crew-quick-model">
199
- <span className="crew-quick-rank" aria-hidden="true">{i + 1}.</span>
200
- <div><div className="crew-quick-model-name">{entry.model}</div><div className="crew-quick-provider">{entry.provider}</div></div>
201
- <div className="crew-quick-actions">
202
- <button type="button" className="crew-quick-btn" aria-label={`${t.moveUp} ${entry.model}`} title={t.moveUp} disabled={busy || i === 0} onClick={() => moveModel(listKey, i, -1)}>↑</button>
203
- <button type="button" className="crew-quick-btn" aria-label={`${t.moveDown} ${entry.model}`} title={t.moveDown} disabled={busy || i === list.length - 1} onClick={() => moveModel(listKey, i, 1)}>↓</button>
204
- <button type="button" className="crew-quick-btn" aria-label={`${t.remove} ${entry.model}`} title={t.remove} disabled={busy} onClick={() => removeModel(listKey, i)}>×</button>
205
- </div>
206
- </li>
207
- ))}
208
- </ol>
209
- <details className="crew-quick-add">
210
- <summary>{t.addModel}</summary>
211
- <div className="crew-quick-form">
212
- <input className="crew-quick-input" aria-label={`${label} Provider`} placeholder={t.providerPlaceholder} disabled={busy}
213
- value={draft.provider} onChange={(e) => setDrafts((d) => ({ ...d, [listKey]: { ...draft, provider: e.target.value } }))} />
214
- <input className="crew-quick-input" aria-label={`${label} ${locale === 'zh' ? '模型' : 'Model'}`} placeholder={t.modelPlaceholder} disabled={busy}
215
- value={draft.model} onChange={(e) => setDrafts((d) => ({ ...d, [listKey]: { ...draft, model: e.target.value } }))} />
216
- <button type="button" className="crew-quick-btn" disabled={busy || !draft.provider.trim() || !draft.model.trim()} onClick={() => addModel(listKey)}>{t.addModel}</button>
217
- </div>
218
- </details>
219
- </div>
220
- </details>
221
- );
222
- };
223
-
224
- if (ready === false) {
225
- return (
226
- <div className="dsh-crew-ui crew-quick-card">
227
- <PanelStyles /><LocalStyles />
228
- <div className="crew-quick-title">{t.title}</div>
229
- <div className="crew-quick-notice">{t.unavailable}</div>
230
- <div className="crew-quick-row">
231
- <a className="crew-nav-link" href={FULL} target="_blank" rel="noopener noreferrer">{t.openDiag}</a>
232
- </div>
233
- </div>
234
- );
235
- }
236
- if (config === null) {
237
- return (<div className="dsh-crew-ui crew-quick-card"><PanelStyles /><LocalStyles /><div className="crew-quick-notice" role="status">{t.working}</div></div>);
238
- }
239
-
240
- return (
241
- <div className="dsh-crew-ui crew-quick-card">
242
- <PanelStyles /><LocalStyles />
243
- <PanelHeader title={t.title} eyebrow={t.surface} description={t.description} href={FULL} linkText={t.openFull}>
244
- <span className="crew-quick-chip">{t.running} · 3210</span>
245
- </PanelHeader>
246
- <div className="crew-quick-row crew-quick-master">
247
- <span className="crew-quick-section">{t.crew}</span>
248
- <label><input type="checkbox" disabled={busy} checked={config.subagents_enabled !== false}
249
- onChange={(e) => toggle('subagents_enabled', e.target.checked)} /> {t.enabled}</label>
250
- </div>
251
- {modelList('flash_model_priority', t.flash)}
252
- {modelList('pro_model_priority', t.pro)}
253
- {restartPending && (
254
- <div className="crew-quick-row">
255
- <a className="crew-quick-btn primary" href={FULL} target="_blank" rel="noopener noreferrer">{t.openFull}</a>
256
- </div>
257
- )}
258
- {notice && <div className="crew-quick-notice" role="status" aria-live="polite">{notice}</div>}
259
- </div>
260
- );
261
- }
262
-
263
- export function applyQuick(ctx: any): void {
264
- ctx.slots.inject('settings.section', () => {
265
- return ctx.slots.register(
266
- {
267
- name: 'settings.section',
268
- id: 'dsh-crew-quick',
269
- order: 65,
270
- label: () => 'DSH Crew',
271
- },
272
- () => <QuickPanel ctx={ctx} />,
273
- );
274
- });
275
- }
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { PanelHeader, PanelStyles } from './panel-chrome';
3
+
4
+ // DSH Crew QUICK CONTROLS panel for the official 3080 surface.
5
+ //
6
+ // Narrow by design: only the user-facing master switch, flash/pro model
7
+ // priority lists — all other operations belong
8
+ // to the native 3210 full control plane. Talks only to the quick endpoints
9
+ // (/_dsh/dsh-crew/quick-config, /quick-status). Runtime maintenance stays on 3210.
10
+
11
+ const API = '/_dsh/dsh-crew';
12
+ const CREW_CONTROL_PLANE_URL = 'http://127.0.0.1:3210/';
13
+ const FULL = 'http://127.0.0.1:3210/';
14
+
15
+ type ModelEntry = { provider: string; model: string };
16
+ type QuickConfig = {
17
+ subagents_enabled?: boolean;
18
+ flash_model_priority?: ModelEntry[];
19
+ pro_model_priority?: ModelEntry[];
20
+ };
21
+
22
+ const RESTART_KEYS = new Set([
23
+ ]);
24
+
25
+ const T = {
26
+ zh: {
27
+ title: 'DSH Crew 快捷控制',
28
+ openFull: '打开 3210 后台 →',
29
+ running: '后台已连接',
30
+ surface: '3080 / 日常控制',
31
+ description: '常用开关与模型顺序在这里调整,更多设置进入后台。',
32
+ priorityHint: '从上到下按优先级排列;从列表移除不会删除 Provider。',
33
+ empty: '未配置优先模型,按现有后备策略选择。',
34
+ models: (n: number) => `${n} 个模型`,
35
+ moveUp: '上移', moveDown: '下移', remove: '从优先级移除',
36
+ unavailable: 'Crew 后端不可用',
37
+ openDiag: '打开诊断',
38
+ crew: 'Crew',
39
+ enabled: '启用子 Agent',
40
+ flash: 'Worker / Flash',
41
+ pro: 'Reviewer / Pro',
42
+ addModel: '+ 添加模型',
43
+ provider: 'Provider',
44
+ applyRestart: '应用并重启 Crew',
45
+ savedNeedsRestart: '配置已保存 · 需要重启 Crew 才会生效',
46
+ saved: '已保存',
47
+ working: '处理中…',
48
+ providerPlaceholder: 'provider',
49
+ modelPlaceholder: 'model',
50
+ },
51
+ en: {
52
+ title: 'DSH Crew Quick Controls',
53
+ openFull: 'Open Crew backend (3210) →',
54
+ running: 'Backend connected',
55
+ surface: '3080 / DAILY CONTROLS',
56
+ description: 'Manage everyday switches and model order here. Open the backend for advanced settings.',
57
+ priorityHint: 'Ordered by priority. Removing a model here does not delete its provider.',
58
+ empty: 'No priority models. The existing fallback policy applies.',
59
+ models: (n: number) => `${n} models`,
60
+ moveUp: 'Move up', moveDown: 'Move down', remove: 'Remove from priority',
61
+ unavailable: 'Crew backend unavailable',
62
+ openDiag: 'Open diagnostics',
63
+ crew: 'Crew',
64
+ enabled: 'Enable sub-agents',
65
+ flash: 'Worker / Flash',
66
+ pro: 'Reviewer / Pro',
67
+ addModel: '+ Add model',
68
+ provider: 'Provider',
69
+ applyRestart: 'Apply & restart Crew',
70
+ savedNeedsRestart: 'Saved · restart Crew to take effect',
71
+ saved: 'Saved',
72
+ working: 'Working…',
73
+ providerPlaceholder: 'provider',
74
+ modelPlaceholder: 'Model',
75
+ },
76
+ };
77
+
78
+ function LocalStyles() {
79
+ return (
80
+ <style>{`
81
+ .dsh-crew-ui.crew-quick-card { display: flex; flex-direction: column; gap: 12px; }
82
+ .crew-quick-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
83
+ .crew-quick-title { font-size: 16px; font-weight: 680; }
84
+ .crew-quick-section { font-weight: 650; opacity: 0.85; }
85
+ .crew-quick-chip { border: 1px solid rgba(128,128,128,0.35); border-radius: 999px; padding: 1px 10px; font-size: 12px; }
86
+ .crew-quick-card .crew-quick-btn { border: 1px solid var(--crew-line); border-radius: 7px; min-height: 30px; padding: 3px 9px; cursor: pointer; background: transparent; color: inherit; font-size: 12px; }
87
+ .crew-quick-btn.primary { border-color: #4a9eff; color: #4a9eff; font-weight: 650; }
88
+ .crew-quick-card .crew-quick-input { border: 1px solid var(--crew-line); border-radius: 6px; padding: 6px 9px; font-size: 12px; min-width: 0; width: 100%; background: transparent; color: inherit; }
89
+ .crew-quick-notice { opacity: 0.75; font-size: 12.5px; }
90
+ .crew-quick-card .crew-quick-master { padding: 11px 14px; border: 1px solid var(--crew-line); border-radius: 10px; justify-content: space-between; }
91
+ .crew-quick-card input[type=checkbox] { accent-color: var(--crew-accent); width: 15px; height: 15px; }
92
+ .crew-quick-card label { display: inline-flex; gap: 7px; align-items: center; }
93
+ .crew-quick-card .crew-quick-group { border: 1px solid var(--crew-line); border-radius: 10px; overflow: hidden; }
94
+ .crew-quick-card .crew-quick-group > summary { cursor: pointer; padding: 12px 14px; }
95
+ .crew-quick-card .crew-quick-count { margin-left: 10px; font-size: 11px; opacity: .65; font-weight: 400; }
96
+ .crew-quick-card .crew-quick-content { padding: 0 14px 14px; }
97
+ .crew-quick-card .crew-quick-list { padding: 0; margin: 10px 0; list-style: none; }
98
+ .crew-quick-card .crew-quick-model { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: 9px; align-items: center; border-top: 1px solid var(--crew-line); padding: 10px 0; }
99
+ .crew-quick-card .crew-quick-rank { opacity: .5; font-size: 11px; font-variant-numeric: tabular-nums; }
100
+ .crew-quick-card .crew-quick-model-name { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
101
+ .crew-quick-card .crew-quick-provider { font-size: 11px; opacity: .65; overflow-wrap: anywhere; }
102
+ .crew-quick-card .crew-quick-actions { display: flex; gap: 4px; }
103
+ .crew-quick-card .crew-quick-add > summary { color: var(--crew-accent); cursor: pointer; font-size: 12px; padding: 4px 0; }
104
+ .crew-quick-card .crew-quick-form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: 7px; margin-top: 9px; }
105
+ .crew-quick-card .crew-quick-media { display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 10px; align-items: center; margin-top: 10px; }
106
+ @media (max-width: 640px) { .crew-quick-card .crew-quick-form { grid-template-columns: minmax(0, 1fr); } .crew-quick-card .crew-quick-model { grid-template-columns: 18px minmax(0, 1fr); } .crew-quick-card .crew-quick-actions { grid-column: 2; justify-content: flex-end; } }
107
+ `}</style>
108
+ );
109
+ }
110
+
111
+ async function readJson(res: Response) {
112
+ if (!res.ok) return { ok: false, status: res.status };
113
+ try { return await res.json(); } catch { return { ok: false }; }
114
+ }
115
+
116
+ export function QuickPanel({ ctx }: { ctx: any }) {
117
+ const locale = ctx?.locale?.getLocale?.().active === 'zh' ? 'zh' : 'en';
118
+ const t = (T as any)[locale] ?? T.zh;
119
+ const [config, setConfig] = useState<QuickConfig | null>(null);
120
+ const [ready, setReady] = useState<boolean | null>(null);
121
+ const [notice, setNotice] = useState('');
122
+ const [busy, setBusy] = useState(false);
123
+ const [restartPending, setRestartPending] = useState(false);
124
+ const [drafts, setDrafts] = useState<Record<string, ModelEntry>>({});
125
+
126
+ const load = useCallback(async () => {
127
+ try {
128
+ const status = await readJson(await fetch(`${API}/quick-status`, { cache: 'no-store' }));
129
+ if (!status.ok) { setReady(false); return; }
130
+ setReady(true);
131
+ setConfig(status.config ?? {});
132
+ } catch { setReady(false); }
133
+ }, []);
134
+
135
+ useEffect(() => { void load(); }, [load]);
136
+
137
+ const patch = useCallback(async (next: QuickConfig) => {
138
+ setBusy(true); setNotice(t.working);
139
+ try {
140
+ const res = await readJson(await fetch(`${API}/quick-config`, {
141
+ method: 'POST', headers: { 'content-type': 'application/json' },
142
+ body: JSON.stringify(next),
143
+ }));
144
+ if (!res.ok) throw new Error(res.code ?? res.error ?? 'save failed');
145
+ setConfig(res.config ?? {});
146
+ const needsRestart = Object.keys(next).some((k) => RESTART_KEYS.has(k));
147
+ if (needsRestart) { setRestartPending(true); setNotice(t.savedNeedsRestart); }
148
+ else { setNotice(t.saved); setTimeout(() => setNotice(''), 1500); }
149
+ return true;
150
+ } catch (e: any) {
151
+ setNotice(String(e?.message ?? e));
152
+ return false;
153
+ } finally { setBusy(false); }
154
+ }, [t]);
155
+
156
+ const toggle = (key: 'subagents_enabled', value: boolean) => {
157
+ setConfig((c) => ({ ...(c ?? {}), [key]: value }));
158
+ void patch({ [key]: value } as QuickConfig);
159
+ };
160
+
161
+
162
+ const moveModel = (listKey: 'flash_model_priority' | 'pro_model_priority', index: number, dir: -1 | 1) => {
163
+ const list = [...((config?.[listKey] as ModelEntry[] | undefined) ?? [])];
164
+ const to = index + dir;
165
+ if (to < 0 || to >= list.length) return;
166
+ [list[index], list[to]] = [list[to], list[index]];
167
+ setConfig((c) => ({ ...(c ?? {}), [listKey]: list }));
168
+ void patch({ [listKey]: list } as unknown as QuickConfig);
169
+ };
170
+
171
+ const removeModel = (listKey: 'flash_model_priority' | 'pro_model_priority', index: number) => {
172
+ const list = [...((config?.[listKey] as ModelEntry[] | undefined) ?? [])];
173
+ list.splice(index, 1);
174
+ setConfig((c) => ({ ...(c ?? {}), [listKey]: list }));
175
+ void patch({ [listKey]: list } as unknown as QuickConfig);
176
+ };
177
+
178
+ const addModel = (listKey: 'flash_model_priority' | 'pro_model_priority') => {
179
+ const draft = drafts[listKey] ?? { provider: '', model: '' };
180
+ if (!draft.provider.trim() || !draft.model.trim()) return;
181
+ const list = [...((config?.[listKey] as ModelEntry[] | undefined) ?? []), { provider: draft.provider.trim(), model: draft.model.trim() }];
182
+ setConfig((c) => ({ ...(c ?? {}), [listKey]: list }));
183
+ setDrafts((d) => ({ ...d, [listKey]: { provider: '', model: '' } }));
184
+ void patch({ [listKey]: list } as unknown as QuickConfig);
185
+ };
186
+
187
+ const modelList = (listKey: 'flash_model_priority' | 'pro_model_priority', label: string) => {
188
+ const list: ModelEntry[] = (config?.[listKey] as ModelEntry[] | undefined) ?? [];
189
+ const draft = drafts[listKey] ?? { provider: '', model: '' };
190
+ return (
191
+ <details className="crew-quick-group" open={listKey === 'flash_model_priority'}>
192
+ <summary><strong>{label}</strong><span className="crew-quick-count">{t.models(list.length)}</span></summary>
193
+ <div className="crew-quick-content">
194
+ <div className="crew-quick-notice">{t.priorityHint}</div>
195
+ {list.length === 0 && <p className="crew-quick-notice">{t.empty}</p>}
196
+ <ol className="crew-quick-list" aria-label={label}>
197
+ {list.map((entry, i) => (
198
+ <li key={`${entry.provider}/${entry.model}/${i}`} className="crew-quick-model">
199
+ <span className="crew-quick-rank" aria-hidden="true">{i + 1}.</span>
200
+ <div><div className="crew-quick-model-name">{entry.model}</div><div className="crew-quick-provider">{entry.provider}</div></div>
201
+ <div className="crew-quick-actions">
202
+ <button type="button" className="crew-quick-btn" aria-label={`${t.moveUp} ${entry.model}`} title={t.moveUp} disabled={busy || i === 0} onClick={() => moveModel(listKey, i, -1)}>↑</button>
203
+ <button type="button" className="crew-quick-btn" aria-label={`${t.moveDown} ${entry.model}`} title={t.moveDown} disabled={busy || i === list.length - 1} onClick={() => moveModel(listKey, i, 1)}>↓</button>
204
+ <button type="button" className="crew-quick-btn" aria-label={`${t.remove} ${entry.model}`} title={t.remove} disabled={busy} onClick={() => removeModel(listKey, i)}>×</button>
205
+ </div>
206
+ </li>
207
+ ))}
208
+ </ol>
209
+ <details className="crew-quick-add">
210
+ <summary>{t.addModel}</summary>
211
+ <div className="crew-quick-form">
212
+ <input className="crew-quick-input" aria-label={`${label} Provider`} placeholder={t.providerPlaceholder} disabled={busy}
213
+ value={draft.provider} onChange={(e) => setDrafts((d) => ({ ...d, [listKey]: { ...draft, provider: e.target.value } }))} />
214
+ <input className="crew-quick-input" aria-label={`${label} ${locale === 'zh' ? '模型' : 'Model'}`} placeholder={t.modelPlaceholder} disabled={busy}
215
+ value={draft.model} onChange={(e) => setDrafts((d) => ({ ...d, [listKey]: { ...draft, model: e.target.value } }))} />
216
+ <button type="button" className="crew-quick-btn" disabled={busy || !draft.provider.trim() || !draft.model.trim()} onClick={() => addModel(listKey)}>{t.addModel}</button>
217
+ </div>
218
+ </details>
219
+ </div>
220
+ </details>
221
+ );
222
+ };
223
+
224
+ if (ready === false) {
225
+ return (
226
+ <div className="dsh-crew-ui crew-quick-card">
227
+ <PanelStyles /><LocalStyles />
228
+ <div className="crew-quick-title">{t.title}</div>
229
+ <div className="crew-quick-notice">{t.unavailable}</div>
230
+ <div className="crew-quick-row">
231
+ <a className="crew-nav-link" href={FULL} target="_blank" rel="noopener noreferrer">{t.openDiag}</a>
232
+ </div>
233
+ </div>
234
+ );
235
+ }
236
+ if (config === null) {
237
+ return (<div className="dsh-crew-ui crew-quick-card"><PanelStyles /><LocalStyles /><div className="crew-quick-notice" role="status">{t.working}</div></div>);
238
+ }
239
+
240
+ return (
241
+ <div className="dsh-crew-ui crew-quick-card">
242
+ <PanelStyles /><LocalStyles />
243
+ <PanelHeader title={t.title} eyebrow={t.surface} description={t.description} href={FULL} linkText={t.openFull}>
244
+ <span className="crew-quick-chip">{t.running} · 3210</span>
245
+ </PanelHeader>
246
+ <div className="crew-quick-row crew-quick-master">
247
+ <span className="crew-quick-section">{t.crew}</span>
248
+ <label><input type="checkbox" disabled={busy} checked={config.subagents_enabled !== false}
249
+ onChange={(e) => toggle('subagents_enabled', e.target.checked)} /> {t.enabled}</label>
250
+ </div>
251
+ {modelList('flash_model_priority', t.flash)}
252
+ {modelList('pro_model_priority', t.pro)}
253
+ {restartPending && (
254
+ <div className="crew-quick-row">
255
+ <a className="crew-quick-btn primary" href={FULL} target="_blank" rel="noopener noreferrer">{t.openFull}</a>
256
+ </div>
257
+ )}
258
+ {notice && <div className="crew-quick-notice" role="status" aria-live="polite">{notice}</div>}
259
+ </div>
260
+ );
261
+ }
262
+
263
+ export function applyQuick(ctx: any): void {
264
+ ctx.slots.inject('settings.section', () => {
265
+ return ctx.slots.register(
266
+ {
267
+ name: 'settings.section',
268
+ id: 'dsh-crew-quick',
269
+ order: 65,
270
+ label: () => 'DSH Crew',
271
+ },
272
+ () => <QuickPanel ctx={ctx} />,
273
+ );
274
+ });
275
+ }
@@ -1,34 +1,34 @@
1
- import { isExactNativeCrewIdentity, sameCompleteRuntimeIdentity } from '../runtime-identity-contract.mjs';
2
- import { validateModelCallabilityV2 } from '../model-callability-contract.mjs';
3
-
4
- export function readinessExpiryDelay(expiresAt, now = Date.now()) {
5
- return Number.isFinite(expiresAt) ? Math.max(0, expiresAt - now + 10) : null;
6
- }
7
-
8
- /**
9
- * Apply one extension response to the atomic runtime/readiness envelope.
10
- * Older responses are ignored so a restart cannot be rolled back by a slow
11
- * request that started before the new runtime identity was observed.
12
- */
13
- export function acceptReadinessResponse(response, { generation, latestGeneration } = {}) {
14
- if (generation !== latestGeneration) return { accepted: false, envelope: null };
15
- const extensionRuntime = response?.extension?.runtime;
16
- const snapshot = response?.extension?.readiness_snapshot;
17
- const valid = response?.ok === true
18
- && isExactNativeCrewIdentity(extensionRuntime)
19
- && isExactNativeCrewIdentity(snapshot?.runtime)
20
- && sameCompleteRuntimeIdentity(extensionRuntime, snapshot.runtime);
21
- const expectedEnabledRoles = {
22
- worker: response?.extension?.capabilities?.['deepseek.worker'] === true,
23
- reviewer: response?.extension?.capabilities?.['deepseek.reviewer'] === true,
24
- };
25
- const projection = valid ? validateModelCallabilityV2({
26
- projection: snapshot.model_callability,
27
- runtime: extensionRuntime,
28
- expectedEnabledRoles,
29
- expectedSelections: { worker: snapshot.worker?.selected ?? null, reviewer: snapshot.reviewer?.selected ?? null },
30
- }) : { ok: false };
31
- return { accepted: true, envelope: valid && projection.ok
32
- ? { runtime: extensionRuntime, snapshot, expiresAt: snapshot.model_callability.expires_at }
33
- : {} };
34
- }
1
+ import { isExactNativeCrewIdentity, sameCompleteRuntimeIdentity } from '../runtime-identity-contract.mjs';
2
+ import { validateModelCallabilityV2 } from '../model-callability-contract.mjs';
3
+
4
+ export function readinessExpiryDelay(expiresAt, now = Date.now()) {
5
+ return Number.isFinite(expiresAt) ? Math.max(0, expiresAt - now + 10) : null;
6
+ }
7
+
8
+ /**
9
+ * Apply one extension response to the atomic runtime/readiness envelope.
10
+ * Older responses are ignored so a restart cannot be rolled back by a slow
11
+ * request that started before the new runtime identity was observed.
12
+ */
13
+ export function acceptReadinessResponse(response, { generation, latestGeneration } = {}) {
14
+ if (generation !== latestGeneration) return { accepted: false, envelope: null };
15
+ const extensionRuntime = response?.extension?.runtime;
16
+ const snapshot = response?.extension?.readiness_snapshot;
17
+ const valid = response?.ok === true
18
+ && isExactNativeCrewIdentity(extensionRuntime)
19
+ && isExactNativeCrewIdentity(snapshot?.runtime)
20
+ && sameCompleteRuntimeIdentity(extensionRuntime, snapshot.runtime);
21
+ const expectedEnabledRoles = {
22
+ worker: response?.extension?.capabilities?.['deepseek.worker'] === true,
23
+ reviewer: response?.extension?.capabilities?.['deepseek.reviewer'] === true,
24
+ };
25
+ const projection = valid ? validateModelCallabilityV2({
26
+ projection: snapshot.model_callability,
27
+ runtime: extensionRuntime,
28
+ expectedEnabledRoles,
29
+ expectedSelections: { worker: snapshot.worker?.selected ?? null, reviewer: snapshot.reviewer?.selected ?? null },
30
+ }) : { ok: false };
31
+ return { accepted: true, envelope: valid && projection.ok
32
+ ? { runtime: extensionRuntime, snapshot, expiresAt: snapshot.model_callability.expires_at }
33
+ : {} };
34
+ }