@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.
- package/.claude-plugin/marketplace.json +16 -16
- package/.claude-plugin/plugin.json +14 -14
- package/LICENSE +21 -21
- package/README.de.md +359 -359
- package/README.es.md +359 -359
- package/README.fr.md +359 -359
- package/README.hi.md +359 -359
- package/README.id.md +359 -359
- package/README.ja.md +359 -359
- package/README.ko.md +359 -359
- package/README.md +126 -126
- package/README.pt.md +359 -359
- package/README.ru.md +359 -359
- package/README.th.md +359 -359
- package/README.tr.md +359 -359
- package/README.vi.md +359 -359
- package/README.zh-TW.md +359 -359
- package/README.zh.md +116 -116
- package/agents/ds-flash.md +24 -24
- package/agents/ds-pro.md +24 -24
- package/agents/ds-reviewer.md +23 -23
- package/agents/ds-worker.md +23 -23
- package/bin/dsh-crew.mjs +16 -16
- package/codex/agents/ds-flash.toml +34 -34
- package/codex/agents/ds-pro.toml +34 -34
- package/codex/agents/ds-reviewer.toml +32 -32
- package/codex/agents/ds-worker.toml +32 -32
- package/codex/prompts/dsh-config.md +19 -19
- package/codex/prompts/dsh-status.md +5 -5
- package/commands/config.md +23 -23
- package/commands/off.md +5 -5
- package/commands/on.md +5 -5
- package/commands/status.md +9 -9
- package/cordis.patch.yml +4 -4
- package/docs/gpt-relay-extension.md +103 -103
- package/docs/installation.md +138 -138
- package/docs/job-contracts.md +103 -103
- package/docs/readiness-matrix.md +85 -85
- package/docs/ui-surfaces.md +107 -107
- package/official-web-bridge/cordis.patch.yml +4 -4
- package/official-web-bridge/entry.mjs +1 -1
- package/official-web-bridge/overlay-entry.mjs +59 -59
- package/official-web-bridge/package.json +25 -25
- package/package.json +1 -1
- package/scripts/build-client.mjs +49 -49
- package/scripts/live-crew-smoke.mjs +39 -39
- package/scripts/live-policy-matrix.mjs +177 -177
- package/scripts/policy-probe.mjs +101 -101
- package/scripts/remove-legacy-official-bridge.ps1 +89 -89
- package/scripts/setup.mjs +393 -393
- package/scripts/smoke-real.mjs +110 -110
- package/scripts/smoke.mjs +78 -78
- package/scripts/verify-crew-ui-polish.mjs +145 -145
- package/scripts/verify-history-ui.mjs +97 -97
- package/scripts/verify-installer-fix.mjs +26 -26
- package/scripts/verify-npm-install.mjs +311 -311
- package/scripts/verify-official-bridge-e2e.mjs +192 -192
- package/src/adaptive-routing.mjs +260 -260
- package/src/client/activation-summary.tsx +64 -64
- package/src/client/collapsible-sections.mjs +55 -55
- package/src/client/history-panel.tsx +108 -108
- package/src/client/host-readiness.mjs +71 -71
- package/src/client/index.tsx +1711 -1711
- package/src/client/model-callability-view.mjs +17 -17
- package/src/client/panel-chrome.tsx +40 -40
- package/src/client/quick-entry.tsx +10 -10
- package/src/client/quick-panel.tsx +275 -275
- package/src/client/readiness-envelope.mjs +34 -34
- package/src/client/surface-detection.mjs +43 -43
- package/src/config-readiness.mjs +226 -226
- package/src/credential-reference.mjs +38 -38
- package/src/delivery.mjs +205 -205
- package/src/dsh-cli-runtime.mjs +1021 -1021
- package/src/dsh-cohort.mjs +20 -20
- package/src/extension-contract.mjs +104 -104
- package/src/failure-classification.mjs +201 -201
- package/src/history/admission-gate.mjs +67 -67
- package/src/history/archive-store.mjs +272 -272
- package/src/history/cleanup-plan.mjs +89 -89
- package/src/history/http.mjs +32 -32
- package/src/history/operation.mjs +86 -86
- package/src/history/runner-detach.mjs +34 -34
- package/src/history/runner.mjs +52 -52
- package/src/history/runtime.mjs +36 -36
- package/src/history/service.mjs +177 -177
- package/src/history/state.mjs +27 -27
- package/src/hub/entry.mjs +104 -104
- package/src/hub/index.mjs +2694 -2694
- package/src/hub-client.mjs +154 -154
- package/src/hub-compatibility.mjs +42 -42
- package/src/i18n.mjs +19 -19
- package/src/information-flow.mjs +67 -67
- package/src/install/cli.mjs +28 -28
- package/src/install/install-legacy.mjs +711 -711
- package/src/install/install.mjs +483 -483
- package/src/install/npx-lifecycle.mjs +3456 -3456
- package/src/install/official-frontend-assets.mjs +78 -78
- package/src/install/official-web.mjs +95 -95
- package/src/install/payload-content.mjs +88 -88
- package/src/install/windows-startup.mjs +236 -236
- package/src/install/windows-supervisor-adapter.mjs +443 -443
- package/src/install/windows-supervisor-lifecycle.mjs +782 -782
- package/src/install/zcode.mjs +397 -397
- package/src/job-contracts.mjs +255 -255
- package/src/local-request-guard.mjs +60 -60
- package/src/mcp-runtime.mjs +340 -332
- package/src/model-callability-contract.mjs +79 -79
- package/src/model-catalog.mjs +180 -180
- package/src/model-routing.mjs +586 -586
- package/src/model-schedule.mjs +207 -207
- package/src/official-web-bridge.mjs +447 -447
- package/src/policy.mjs +235 -235
- package/src/provider-delete-adapters.mjs +1934 -1934
- package/src/provider-health.mjs +130 -130
- package/src/provider-inventory.mjs +182 -182
- package/src/provider-layer-migration-adapters.mjs +759 -759
- package/src/provider-layer-migration.mjs +198 -198
- package/src/provider-lifecycle-state.mjs +103 -103
- package/src/provider-lifecycle.mjs +252 -252
- package/src/provider-profile-store.mjs +390 -390
- package/src/provider-settings-store.mjs +633 -633
- package/src/provider-store-lock.mjs +67 -67
- package/src/readiness-matrix.mjs +181 -181
- package/src/removable-waiter.mjs +29 -29
- package/src/role-profiles.mjs +107 -107
- package/src/runtime-controls.mjs +84 -84
- package/src/runtime-identity-contract.mjs +34 -34
- package/src/runtime-identity.mjs +235 -235
- package/src/runtime-readiness-snapshot.mjs +285 -285
- package/src/server.mjs +581 -581
- package/src/session-origins.mjs +60 -60
- package/src/standalone-sdk.mjs +23 -23
- package/src/status-shard.mjs +63 -63
- package/src/structured-error-code.mjs +38 -38
- package/src/supervisor/restart-request.mjs +256 -256
- package/src/workflow-runtime.mjs +739 -739
- package/src/workflow.mjs +155 -155
- package/src/workspace-audit.mjs +231 -231
- package/src/workspace-context.mjs +146 -146
- package/src/workspace-isolation.mjs +455 -401
- package/src/workspace-readiness.mjs +32 -32
- package/statusline/statusline.sh +14 -14
- package/statusline/worker-segment.sh +35 -35
- package/windows/start-dsh-crew.cmd +57 -57
- package/windows/start-dsh-crew.ps1 +1302 -1302
- package/windows/supervisor-control.ps1 +467 -467
- package/worker.cordis.yml +67 -67
- package/zcode/agents/ds-reviewer.md +31 -31
- package/zcode/agents/ds-worker.md +31 -31
- package/zcode/commands/dsh-config.md +17 -17
- 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
|
+
}
|