@ran-sh/dsh-crew 1.5.1 → 1.6.0

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.
@@ -62,12 +62,7 @@ function PanelHeader({ title, eyebrow, description, href, linkText, children })
62
62
  //#region src/client/quick-panel.tsx
63
63
  const API = "/_dsh/dsh-crew";
64
64
  const FULL = "http://127.0.0.1:3210/";
65
- const RESTART_KEYS = /* @__PURE__ */ new Set([
66
- "vision_enabled",
67
- "imagegen_enabled",
68
- "vision_provider",
69
- "imagegen_provider"
70
- ]);
65
+ const RESTART_KEYS = /* @__PURE__ */ new Set([]);
71
66
  const T = {
72
67
  zh: {
73
68
  title: "DSH Crew 快捷控制",
@@ -88,9 +83,6 @@ const T = {
88
83
  flash: "Worker / Flash",
89
84
  pro: "Reviewer / Pro",
90
85
  addModel: "+ 添加模型",
91
- multimodal: "多模态",
92
- vision: "视觉",
93
- imagegen: "生图",
94
86
  provider: "Provider",
95
87
  applyRestart: "应用并重启 Crew",
96
88
  savedNeedsRestart: "配置已保存 · 需要重启 Crew 才会生效",
@@ -118,9 +110,6 @@ const T = {
118
110
  flash: "Worker / Flash",
119
111
  pro: "Reviewer / Pro",
120
112
  addModel: "+ Add model",
121
- multimodal: "Multimodal",
122
- vision: "Vision",
123
- imagegen: "Imagegen",
124
113
  provider: "Provider",
125
114
  applyRestart: "Apply & restart Crew",
126
115
  savedNeedsRestart: "Saved · restart Crew to take effect",
@@ -229,13 +218,6 @@ function QuickPanel({ ctx }) {
229
218
  }));
230
219
  patch({ [key]: value });
231
220
  };
232
- const setProvider = (key, value) => {
233
- setConfig((c) => ({
234
- ...c ?? {},
235
- [key]: value
236
- }));
237
- patch({ [key]: value });
238
- };
239
221
  const moveModel = (listKey, index, dir) => {
240
222
  const list = [...config?.[listKey] ?? []];
241
223
  const to = index + dir;
@@ -473,58 +455,6 @@ function QuickPanel({ ctx }) {
473
455
  }),
474
456
  modelList("flash_model_priority", t.flash),
475
457
  modelList("pro_model_priority", t.pro),
476
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
477
- className: "crew-quick-group",
478
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.multimodal }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
479
- className: "crew-quick-count",
480
- children: [
481
- t.vision,
482
- " / ",
483
- t.imagegen
484
- ]
485
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
486
- className: "crew-quick-content",
487
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
488
- className: "crew-quick-media",
489
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
490
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
491
- type: "checkbox",
492
- disabled: busy,
493
- checked: config.vision_enabled === true,
494
- onChange: (e) => toggle("vision_enabled", e.target.checked)
495
- }),
496
- " ",
497
- t.vision
498
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
499
- className: "crew-quick-input",
500
- "aria-label": `${t.vision} Provider`,
501
- placeholder: t.provider,
502
- disabled: busy,
503
- defaultValue: config.vision_provider ?? "",
504
- onBlur: (e) => setProvider("vision_provider", e.target.value)
505
- }, `vision-${config.vision_provider ?? ""}`)]
506
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
507
- className: "crew-quick-media",
508
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
509
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
510
- type: "checkbox",
511
- disabled: busy,
512
- checked: config.imagegen_enabled === true,
513
- onChange: (e) => toggle("imagegen_enabled", e.target.checked)
514
- }),
515
- " ",
516
- t.imagegen
517
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
518
- className: "crew-quick-input",
519
- "aria-label": `${t.imagegen} Provider`,
520
- placeholder: t.provider,
521
- disabled: busy,
522
- defaultValue: config.imagegen_provider ?? "",
523
- onBlur: (e) => setProvider("imagegen_provider", e.target.value)
524
- })]
525
- })]
526
- })]
527
- }),
528
458
  restartPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
529
459
  className: "crew-quick-row",
530
460
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ran-sh/dsh-crew",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "main": "./src/hub/entry.mjs",
6
6
  "bin": {
@@ -4,13 +4,11 @@ export const SETTINGS_SECTION_IDS = Object.freeze([
4
4
  'integrations',
5
5
  'workflow',
6
6
  'flash',
7
- 'pro',
8
- 'dispatch',
9
- 'adaptive',
10
- 'runtime',
11
- 'multimodal',
12
- 'harnessProviders',
13
- 'providers',
7
+ 'pro',
8
+ 'dispatch',
9
+ 'adaptive',
10
+ 'runtime',
11
+ 'harnessProviders',
14
12
  'jobs',
15
13
  ]);
16
14
 
@@ -63,7 +63,7 @@ const COPY = {
63
63
  expandAll: '全部展开', collapseAll: '全部折叠',
64
64
  modelCount: (count: number) => `${count} 个模型`, providerCount: (count: number) => `${count} 个 Provider`,
65
65
  jobCount: (count: number) => `${count} 个任务`, runningCount: (count: number) => `${count} 个运行中`,
66
- sectionNames: { integrations: 'Codex / Claude / ZCode 集成状态', workflow: 'Crew 工作流设置', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: '模型优先级与派发', schedule: '波峰波谷调度', adaptive: '自适应路由', runtime: '运行 / 生效边界', multimodal: '视觉与生图', harnessProviders: 'Harness Providers', providers: '多模态适配器', jobs: '任务状态' },
66
+ sectionNames: { integrations: 'Codex / Claude / ZCode 集成状态', workflow: 'Crew 工作流设置', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: '模型优先级与派发', schedule: '波峰波谷调度', adaptive: '自适应路由', runtime: '运行 / 生效边界', harnessProviders: 'Harness Providers', jobs: '任务状态' },
67
67
  harnessHint: 'DeepSeek 官方 Harness 的 dsh-crew profile:Provider、Harness Models 与运行时配置',
68
68
  hostReadiness: '宿主集成就绪度', hostReadinessHint: '只使用结构化安装与运行时证据;缺少证据不会显示 READY。',
69
69
  readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew plugin profile', official_bridge: 'Official bridge' },
@@ -100,19 +100,11 @@ const COPY = {
100
100
  proReviewsFlash: 'Pro 复审 Flash 改动',
101
101
  proReviewsFlashHint: 'Flash 成功后自动追加一次 Pro 复审(仅 blocking 派发)',
102
102
  proReviewsLocked: 'Review Pipeline 模式固定开启',
103
- enableCrewVision: '启用 Crew 视觉',
104
- enableCrewVisionHint: '关闭后不注册 Crew 的 describe_image 与视觉 route(可与你的 dsh-vision 等共存)。',
105
- enableImagegen: '启用生图',
106
- enableImagegenHint: '关闭后不注册 Crew 的 generate_image。',
107
- capRestartHint: '该开关改变工具注册,需重启 DSH 生效',
108
- capDisabledNote: 'Crew 工具/route 已禁用(重启 DSH 后生效)',
109
103
  tier: '默认档位', effort: '默认推理', mode: '执行模式', timeout: '默认超时(秒)', hubUrl: 'Hub 地址',
110
104
  tierPolicy: '档位策略', escalate: '失败升档',
111
105
  tierPolicyDesc: { auto: 'auto · orchestrator 自选', 'flash-only': '只用 flash', 'pro-only': '只用 pro' },
112
106
  escalateHint: 'flash 失败自动用 pro 重试一次',
113
107
  presetFlash: 'flash 模式', presetPro: 'pro 模式',
114
- multimodal: '多模态',
115
- visionProvider: '视觉 provider', visionModel: '视觉模型', imagegenProvider: '生图 provider',
116
108
  harnessProviders: 'Harness Providers', harnessProviderHint: 'DeepSeek 官方 Harness 的 3210 dsh-crew profile 中的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。',
117
109
  providerState: (state: string) => `状态:${state}`, providerModels: (count: number) => `${count} 个模型`, providerJobs: (count: number) => `${count} 个运行任务`,
118
110
  providerOfficialBuiltin: '· 官方内置(不可删除)', providerSourceUnresolved: '· 来源未解析(安全锁定)',
@@ -123,30 +115,11 @@ const COPY = {
123
115
  providerNoInventory: '暂时无法读取 3210 Provider inventory。',
124
116
  credentialReferences: 'Credential 引用', credentialReferenceHint: '仅显示引用名、归属和孤儿状态;不会读取或删除密钥。',
125
117
  credentialOrphan: '孤儿 · 可单独申请清理', credentialInUse: (count: number) => `使用中 · ${count} 个 Provider`, credentialPurgePlan: '申请清理', credentialPurgeConfirm: '这是不可恢复的 Crew-owned 凭据清理,确认继续?', credentialPurging: '清理中…', credentialPurgeUnavailable: '凭据清理不可用', credentialPurgeUnverified: '已执行清理,但验证失败;引用仍保留用于恢复检查',
126
- customProviders: '多模态适配器', addProvider: '+ 添加多模态适配器',
127
- noCustomProviders: '暂无。添加后会出现在上方的视觉 / 生图 provider 选择里。',
128
- providerName: '名称', modelsField: '模型列表(逗号分隔)',
129
- providerType: '接入方式', typeApi: 'API · 兼容 OpenAI 接口', typeCli: 'CLI · 本地命令',
130
- baseUrl: 'Base URL', apiKey: 'API Key', imagegenModel: '生图模型(留空=不提供生图)',
131
- visionCmd: '视觉命令(可选)', imagegenCmd: '生图命令(可选)',
132
- apiFormHint: '走 OpenAI 兼容接口:视觉调 {base}/chat/completions(图片以 base64 内联),生图调 {base}/images/generations。Key 保存在本机 ~/.config/dsh-crew/config.json,不会外发到第三方。',
133
- cliFormHint: '命令经 bash 执行,占位符会被安全引用后代入。视觉: {image} {question} {model},stdout 即答案;生图: {prompt} {output} {size},命令须把图片写到 {output}。两条命令至少填一条。',
134
- saveProvider: '保存', cancel: '取消', edit: '编辑', del: '删除',
135
- testProvider: '连通测试', testing: '测试中…',
136
- testTip: '按当前填写的内容实测:API 会检查可达性与鉴权并真发一次视觉请求;CLI 会检查可执行文件并真跑一次视觉命令。生图只校验配置,不实际出图。',
137
- testPass: '连通正常', testFail: '连通失败',
138
118
  staleHub: '本 DSH 实例还在跑旧版插件,缺少该接口 —— 重启 DSH 后再试',
139
119
  emptyResponse: (path: string, status: number) => `${path} → HTTP ${status},响应为空`,
140
120
  badJson: (path: string, body: string) => `${path} → 非 JSON 响应: ${body}`,
141
- refreshModels: '重新从 CLI 获取模型列表',
142
121
  presetDefault: (id?: string) => `default · 跟随 DSH${id ? ` (${id})` : ''}`,
143
122
  progressTip: (t: string, turn: number, step: number, calls: number | string) => `耗时 ${t} · 第${turn}轮第${step}步 · ${calls} 次工具调用`,
144
- confirmDeleteProvider: (n: string) => `删除自定义 provider「${n}」?`,
145
- needName: '⚠ 名称必填', needCmd: '⚠ 视觉/生图命令至少填一条',
146
- needBaseUrl: '⚠ Base URL 必填', needModels: '⚠ 模型列表至少填一个',
147
- keySet: '已配置', keyPlaceholder: 'sk-…(留空则不发送 Authorization)',
148
- addModelTip: '添加模型到列表', removeModelTip: '从列表移除当前模型', modelPlaceholder: '模型 id,回车确认',
149
- capVision: '视觉', capImagegen: '生图',
150
123
  groupDispatch: '派发', groupRuntime: '运行', groupMultimodal: '多模态',
151
124
  groupWorkflow: '工作流 / 执行(v0.2)',
152
125
  roleWorker: 'Worker 角色(执行)', roleReviewer: 'Reviewer 角色(独立审查)',
@@ -163,8 +136,6 @@ const COPY = {
163
136
  legacyCompatibility: '↓ 以下为兼容配置(legacy,Flash/Pro 仍可编辑)',
164
137
  cardDispatch: { t: '派发策略', d: 'orchestrator 未指定时的档位与推理强度,以及档位约束、失败升档与两档各自挂载的 Agent 预设。' },
165
138
  cardRuntime: { t: '执行与连接', d: 'worker 会话跑在本实例内还是独立进程,单个任务的超时上限,以及 CC / Codex 探测本实例的地址。' },
166
- cardMM: { t: '视觉与生图', d: '给纯文本的 DSH 模型借来眼睛和画笔:describe_image、会话贴图转写与 generate_image 都用这里的设置。' },
167
- cardCustomProv: { t: '自定义 Provider', d: '接入自己的 API 或本地命令;保存后会出现在上面的视觉 / 生图 provider 选择里。' },
168
139
  modeDesc: { auto: 'auto · 优先 3210', hub: 'hub · 必须 3210' },
169
140
  save: '保存', saved: '已保存', jobs: 'Worker 任务', empty: '当前没有 Worker / Reviewer 任务。',
170
141
  adaptiveTitle: '自适应模型路由(实验)', adaptiveHint: '默认关闭;只重排系统自动候选,手动模型优先级始终保持原序。健康信号仅来自本进程已观察到的成功、失败、超时与粗粒度延迟,重启后清空。',
@@ -211,7 +182,7 @@ const COPY = {
211
182
  expandAll: 'Expand all', collapseAll: 'Collapse all',
212
183
  modelCount: (count: number) => `${count} models`, providerCount: (count: number) => `${count} providers`,
213
184
  jobCount: (count: number) => `${count} jobs`, runningCount: (count: number) => `${count} running`,
214
- sectionNames: { integrations: 'Codex / Claude / ZCode integration status', workflow: 'Crew workflow settings', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: 'Model priority & dispatch', schedule: 'Peak / off-peak scheduling', adaptive: 'Adaptive routing', runtime: 'Runtime / activation boundaries', multimodal: 'Vision & image generation', harnessProviders: 'Harness Providers', providers: 'Multimodal adapters', jobs: 'Task status' },
185
+ sectionNames: { integrations: 'Codex / Claude / ZCode integration status', workflow: 'Crew workflow settings', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: 'Model priority & dispatch', schedule: 'Peak / off-peak scheduling', adaptive: 'Adaptive routing', runtime: 'Runtime / activation boundaries', harnessProviders: 'Harness Providers', jobs: 'Task status' },
215
186
  harnessHint: 'The official DeepSeek Harness dsh-crew profile: providers, Harness Models, and runtime configuration',
216
187
  hostReadiness: 'Host integration readiness', hostReadinessHint: 'Uses structured installer and runtime evidence only; missing evidence is never READY.',
217
188
  readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew plugin profile', official_bridge: 'Official bridge' },
@@ -248,20 +219,12 @@ const COPY = {
248
219
  proReviewsFlash: 'Pro reviews Flash changes',
249
220
  proReviewsFlashHint: 'Automatically follow a successful Flash run with one Pro review (blocking dispatch only)',
250
221
  proReviewsLocked: 'always on in Review Pipeline mode',
251
- enableCrewVision: 'Enable Crew Vision',
252
- enableCrewVisionHint: 'When off, the Crew describe_image tool and vision route are not registered (coexist with your own dsh-vision or other plugins).',
253
- enableImagegen: 'Enable Image Generation',
254
- enableImagegenHint: 'When off, the Crew generate_image tool is not registered.',
255
- capRestartHint: 'This switch changes tool registration and takes effect after a DSH restart',
256
- capDisabledNote: 'Crew tool/route disabled (effective after DSH restart)',
257
222
  tier: 'Default tier', effort: 'Default effort', mode: 'Mode', timeout: 'Timeout (s)', hubUrl: 'Hub URL',
258
223
  tierPolicy: 'Tier policy', escalate: 'Escalate on failure',
259
224
  tierPolicyDesc: { auto: 'auto · orchestrator picks', 'flash-only': 'flash only', 'pro-only': 'pro only' },
260
225
  escalateHint: 'retry a failed flash run once on pro',
261
226
  presetFlash: 'flash preset', presetPro: 'pro preset',
262
- multimodal: 'Multimodal',
263
- visionProvider: 'Vision provider', visionModel: 'Vision model', imagegenProvider: 'Image-gen provider',
264
- harnessProviders: 'Harness Providers', harnessProviderHint: 'Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210; separate from vision / image-gen adapters.',
227
+ harnessProviders: 'Harness Providers', harnessProviderHint: 'Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210.',
265
228
  providerState: (state: string) => `State: ${state}`, providerModels: (count: number) => `${count} models`, providerJobs: (count: number) => `${count} running jobs`,
266
229
  providerOfficialBuiltin: '· official built-in (immutable)', providerSourceUnresolved: '· source unresolved (locked)',
267
230
  providerLayerMigration: 'Legacy profile Providers detected', providerLayerMigrationHint: 'These Providers still exist in the Crew profile/base layer, so the native 3210 page cannot show Delete. Promote them to Harness user settings with an explicit plan; keys are never copied.', providerLayerMigrationPlan: 'Migration plan', providerLayerMigrate: 'Migrate to user layer', providerLayerMigrating: 'Migrating…', providerLayerMigrationConfirm: 'Move this Provider into Harness user settings? A rollback snapshot is saved first; credential values are never copied.', providerLayerRollback: 'Rollback migration',
@@ -271,30 +234,11 @@ const COPY = {
271
234
  providerNoInventory: '3210 Provider inventory is temporarily unavailable.',
272
235
  credentialReferences: 'Credential references', credentialReferenceHint: 'Names, ownership, and orphan status only; values are never read or deleted.',
273
236
  credentialOrphan: 'orphan · separate purge approval required', credentialInUse: (count: number) => `in use · ${count} provider(s)`, credentialPurgePlan: 'Purge…', credentialPurgeConfirm: 'This irreversibly removes a Crew-owned credential. Continue?', credentialPurging: 'Purging…', credentialPurgeUnavailable: 'Credential purge unavailable', credentialPurgeUnverified: 'Purge ran but verification failed; the reference remains visible for recovery',
274
- customProviders: 'Multimodal adapters', addProvider: '+ Add multimodal adapter',
275
- noCustomProviders: 'None yet. Added providers appear in the vision / image-gen selects above.',
276
- providerName: 'Name', modelsField: 'Models (comma-separated)',
277
- providerType: 'Connection', typeApi: 'API · OpenAI-compatible', typeCli: 'CLI · local command',
278
- baseUrl: 'Base URL', apiKey: 'API key', imagegenModel: 'Image-gen model (empty = no image generation)',
279
- visionCmd: 'Vision command (optional)', imagegenCmd: 'Image-gen command (optional)',
280
- apiFormHint: 'OpenAI-compatible: vision calls {base}/chat/completions (image inlined as base64), image-gen calls {base}/images/generations. The key is stored locally in ~/.config/dsh-crew/config.json and never sent to third parties.',
281
- cliFormHint: 'Commands run via bash with shell-quoted placeholders. Vision: {image} {question} {model}, stdout is the answer; image-gen: {prompt} {output} {size}, the command must write the image to {output}. Fill in at least one command.',
282
- saveProvider: 'Save', cancel: 'Cancel', edit: 'Edit', del: 'Delete',
283
- testProvider: 'Test', testing: 'Testing…',
284
- testTip: 'Probes what is currently filled in: API checks reachability and auth then makes one real vision call; CLI checks the executable then runs the vision command once. Image generation is only validated, never actually run.',
285
- testPass: 'Connection OK', testFail: 'Connection failed',
286
237
  staleHub: 'This DSH instance is still running an older build of the plugin and lacks this route — restart DSH and retry',
287
238
  emptyResponse: (path: string, status: number) => `${path} → HTTP ${status}, empty response`,
288
239
  badJson: (path: string, body: string) => `${path} → non-JSON response: ${body}`,
289
- refreshModels: 'Re-fetch the model list from the CLI',
290
240
  presetDefault: (id?: string) => `default · follow DSH${id ? ` (${id})` : ''}`,
291
241
  progressTip: (t: string, turn: number, step: number, calls: number | string) => `${t} elapsed · turn ${turn}, step ${step} · ${calls} tool calls`,
292
- confirmDeleteProvider: (n: string) => `Delete custom provider "${n}"?`,
293
- needName: '⚠ Name is required', needCmd: '⚠ Fill in at least one command',
294
- needBaseUrl: '⚠ Base URL is required', needModels: '⚠ Add at least one model',
295
- keySet: 'set', keyPlaceholder: 'sk-… (leave empty to send no Authorization header)',
296
- addModelTip: 'Add a model to the list', removeModelTip: 'Remove the current model from the list', modelPlaceholder: 'model id, Enter to confirm',
297
- capVision: 'vision', capImagegen: 'image-gen',
298
242
  groupDispatch: 'Dispatch', groupRuntime: 'Runtime', groupMultimodal: 'Multimodal',
299
243
  groupWorkflow: 'Workflow / Execution (v0.2)',
300
244
  roleWorker: 'Worker role (execution)', roleReviewer: 'Reviewer role (independent review)',
@@ -311,8 +255,6 @@ const COPY = {
311
255
  legacyCompatibility: '↓ Legacy compatibility controls below (Flash/Pro still editable)',
312
256
  cardDispatch: { t: 'Dispatch policy', d: 'Tier and effort used when the orchestrator names none, plus tier clamping, failure escalation and the Agent preset mounted per tier.' },
313
257
  cardRuntime: { t: 'Execution & connection', d: 'Whether worker sessions run inside this instance or a separate process, the per-job timeout, and the address CC / Codex probes.' },
314
- cardMM: { t: 'Vision & image generation', d: "Lends the harness's text-only models eyes and a brush: describe_image, pasted-image transcription and generate_image all use these settings." },
315
- cardCustomProv: { t: 'Custom providers', d: 'Bring your own API or local command; saved providers appear in the vision / image-gen selects above.' },
316
258
  modeDesc: { auto: 'auto · prefer 3210', hub: 'hub · require 3210' },
317
259
  save: 'Save', saved: 'Saved', jobs: 'Worker jobs', empty: 'No Worker / Reviewer jobs yet.',
318
260
  adaptiveTitle: 'Adaptive model routing (experimental)', adaptiveHint: 'Off by default. Only system-derived candidates may be reordered; explicit model priorities always keep their order. Health uses only process-local success, failure, timeout, and coarse latency observations and resets on restart.',
@@ -549,11 +491,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
549
491
  const [presetOptions, setPresetOptions] = useState<Array<{ value: string; label?: string }>>([{ value: 'default' }]);
550
492
  const [notice, setNotice] = useState('');
551
493
  const [busy, setBusy] = useState(false);
552
- const [provForm, setProvForm] = useState<{
553
- originalId?: string; name: string; type: 'api' | 'cli'; models: string;
554
- base_url: string; api_key: string; imagegen_model: string;
555
- vision_command: string; imagegen_command: string;
556
- } | null>(null);
557
494
  const [modelAdd, setModelAdd] = useState<string | null>(null);
558
495
  const [modelCatalog, setModelCatalog] = useState<any>(null);
559
496
  const [modelCatalogError, setModelCatalogError] = useState('');
@@ -567,7 +504,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
567
504
  const [providerLifecycleBusy, setProviderLifecycleBusy] = useState<string | null>(null);
568
505
  const [modelPickerTier, setModelPickerTier] = useState<'flash' | 'pro' | null>(null);
569
506
  const [modelQuery, setModelQuery] = useState('');
570
- const [testResult, setTestResult] = useState<{ key: string; ok?: boolean; steps?: any[]; error?: string; busy: boolean } | null>(null);
571
507
  const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>(() => readSectionState(sectionStorage()));
572
508
  const [surface, setSurface] = useState<string>('detecting');
573
509
  const [readinessEnvelope, setReadinessEnvelope] = useState<{ runtime?: any; snapshot?: any; expiresAt?: number | null }>({});
@@ -599,13 +535,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
599
535
  useEffect(() => {
600
536
  if (providerInventoryError) setExpandedSections((current) => openSections(current, ['harnessProviders']));
601
537
  }, [providerInventoryError]);
602
-
603
- useEffect(() => {
604
- if (testResult && !testResult.busy && testResult.ok === false) {
605
- setExpandedSections((current) => openSections(current, ['providers']));
606
- }
607
- }, [testResult]);
608
-
609
538
  useEffect(() => {
610
539
  if (jobs.some((job) => job.status === 'running')) {
611
540
  setExpandedSections((current) => openSections(current, ['jobs']));
@@ -838,119 +767,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
838
767
  };
839
768
 
840
769
  // --- custom providers & user-added models -------------------------------
841
- const customProviders: any[] = config?.custom_providers ?? [];
842
- const extraModels: Record<string, string[]> = config?.extra_models ?? {};
843
- const RESERVED_IDS = ['claude-code', 'codex', 'grok', 'agy', 'off', 'custom', 'default'];
844
- const provType = (p: any): 'api' | 'cli' => p.type ?? ((p.vision_command || p.imagegen_command) ? 'cli' : 'api');
845
- const canSee = (p: any) => (provType(p) === 'cli' ? !!p.vision_command : !!p.base_url && (p.models?.length ?? 0) > 0);
846
- const canDraw = (p: any) => (provType(p) === 'cli' ? !!p.imagegen_command : !!p.base_url && !!String(p.imagegen_model ?? '').trim());
847
- const visionProviderOptions = [
848
- { value: 'claude-code' }, { value: 'codex' }, { value: 'grok' }, { value: 'agy' },
849
- ...customProviders.filter(canSee).map((p) => ({ value: p.id, label: p.name || p.id })),
850
- { value: 'off' },
851
- ];
852
- const imagegenProviderOptions = [
853
- { value: 'codex' }, { value: 'agy', label: 'agy (nano banana)' }, { value: 'grok', label: 'grok (imagine)' },
854
- ...customProviders.filter(canDraw).map((p) => ({ value: p.id, label: p.name || p.id })),
855
- { value: 'off' },
856
- ];
857
- const visionModelOptions = (() => {
858
- if (!config) return [];
859
- const p = config.vision_provider;
860
- const custom = customProviders.find((c) => c.id === p);
861
- const base: Array<{ value: string; label?: string }> = custom
862
- ? [{ value: 'default' }, ...(custom.models ?? []).map((m: string) => ({ value: m }))]
863
- : dynModels[p] ?? (VISION_MODELS[p] ?? ['default']).map((m) => ({ value: m }));
864
- const extras = (extraModels[p] ?? []).filter((m) => !base.some((o) => o.value === m)).map((m) => ({ value: m }));
865
- return [...base, ...extras];
866
- })();
867
- const commitModelAdd = () => {
868
- const m = (modelAdd ?? '').trim();
869
- setModelAdd(null);
870
- if (m === '' || !config) return;
871
- const p = config.vision_provider;
872
- const cur = extraModels[p] ?? [];
873
- const nextExtra = { ...extraModels, [p]: cur.includes(m) ? cur : [...cur, m] };
874
- setConfig((c: any) => ({ ...c, extra_models: nextExtra, vision_model: m }));
875
- void applyPatch({ extra_models: nextExtra, vision_model: m });
876
- };
877
- const removeCurrentModel = () => {
878
- if (!config) return;
879
- const p = config.vision_provider;
880
- const m = config.vision_model;
881
- const nextExtra = { ...extraModels, [p]: (extraModels[p] ?? []).filter((x) => x !== m) };
882
- const fallback = visionModelOptions.find((o) => o.value !== m)?.value ?? 'default';
883
- setConfig((c: any) => ({ ...c, extra_models: nextExtra, vision_model: fallback }));
884
- void applyPatch({ extra_models: nextExtra, vision_model: fallback });
885
- };
886
- const saveProviderForm = () => {
887
- if (!provForm || !config) return;
888
- const name = provForm.name.trim();
889
- if (name === '') { setNotice(copy.needName); return; }
890
- const models = provForm.models.split(/[,,\n]+/).map((s) => s.trim()).filter(Boolean);
891
- const vision_command = provForm.vision_command.trim();
892
- const imagegen_command = provForm.imagegen_command.trim();
893
- if (provForm.type === 'cli') {
894
- if (vision_command === '' && imagegen_command === '') { setNotice(copy.needCmd); return; }
895
- } else {
896
- if (provForm.base_url.trim() === '') { setNotice(copy.needBaseUrl); return; }
897
- if (models.length === 0) { setNotice(copy.needModels); return; }
898
- }
899
- let id = provForm.originalId;
900
- if (!id) {
901
- const base = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'provider';
902
- id = base;
903
- let n = 2;
904
- while (RESERVED_IDS.includes(id) || customProviders.some((p) => p.id === id)) id = `${base}-${n++}`;
905
- }
906
- const entry = provForm.type === 'api'
907
- ? {
908
- id, name, type: 'api', models,
909
- base_url: provForm.base_url.trim(),
910
- api_key: provForm.api_key,
911
- imagegen_model: provForm.imagegen_model.trim(),
912
- }
913
- : { id, name, type: 'cli', models, vision_command, imagegen_command };
914
- const next = provForm.originalId
915
- ? customProviders.map((p) => (p.id === provForm.originalId ? entry : p))
916
- : [...customProviders, entry];
917
- field('custom_providers', next);
918
- setProvForm(null);
919
- };
920
- const runProviderTest = (key: string, entry: any) => {
921
- setTestResult({ key, busy: true });
922
- void post('/provider-test', entry)
923
- .then((r) => setTestResult(r.ok
924
- ? { key, busy: false, ok: r.result.ok, steps: r.result.steps }
925
- : { key, busy: false, ok: false, error: r.error }))
926
- .catch((e) => setTestResult({ key, busy: false, ok: false, error: String(e?.message ?? e) }));
927
- };
928
- /** Shared renderer for a test outcome under the button that triggered it. */
929
- const testReport = (key: string) => {
930
- if (!testResult || testResult.key !== key) return null;
931
- if (testResult.busy) return <div style={{ fontSize: 11.5, opacity: 0.6 }}>{copy.testing}</div>;
932
- return (
933
- <div style={{ fontSize: 11.5, display: 'flex', flexDirection: 'column', gap: 2 }}>
934
- <span style={{ color: testResult.ok ? '#3fb950' : '#f85149' }}>
935
- {testResult.ok ? `✓ ${copy.testPass}` : `✗ ${copy.testFail}`}
936
- </span>
937
- {testResult.error && <span style={{ opacity: 0.7, wordBreak: 'break-all' as const }}>{testResult.error}</span>}
938
- {(testResult.steps ?? []).map((st: any, i: number) => (
939
- <span key={i} style={{ opacity: 0.7, wordBreak: 'break-all' as const }}>
940
- <span style={{ color: st.ok ? '#3fb950' : '#f85149' }}>{st.ok ? '✓' : '✗'}</span> {st.name}: {st.detail}
941
- </span>
942
- ))}
943
- </div>
944
- );
945
- };
946
- const deleteProvider = (id: string) => {
947
- if (!config) return;
948
- const patch: any = { custom_providers: customProviders.filter((p) => p.id !== id) };
949
- if (config.vision_provider === id) { patch.vision_provider = 'claude-code'; patch.vision_model = 'haiku'; }
950
- if (config.imagegen_provider === id) patch.imagegen_provider = 'codex';
951
- setConfig((c: any) => ({ ...c, ...patch }));
952
- void applyPatch(patch);
953
- };
954
770
 
955
771
  const deleteHarnessProvider = async (record: any) => {
956
772
  if (!record || record.delete_capability !== 'supported' || record.desired_state === 'absent' || Number(record.references?.active_jobs ?? 0) > 0) {
@@ -1638,77 +1454,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
1638
1454
  <ActivationSummary activation={config.config_activation} locale={locale} />
1639
1455
  </CollapsibleSection>
1640
1456
 
1641
- <CollapsibleSection sectionId="multimodal" title={copy.sectionNames.multimodal}
1642
- summary={sectionSummary(config.vision_enabled ? `${copy.capVision}: ${config.vision_provider}` : `${copy.capVision}: off`, config.imagegen_enabled ? `${copy.capImagegen}: ${config.imagegen_provider}` : `${copy.capImagegen}: off`)}
1643
- expanded={!!expandedSections.multimodal} onToggle={() => toggleSection('multimodal')}>
1644
- {block(copy.cardMM, (<>
1645
- <label style={{ ...S.field, flexDirection: 'row' as const, alignItems: 'center', gap: 6, gridColumn: '1 / -1' }} title={copy.capRestartHint}>
1646
- <input type="checkbox" checked={!!config.vision_enabled} onChange={(e) => field('vision_enabled', e.target.checked)} />
1647
- <span style={{ fontSize: 12.5 }}>{copy.enableCrewVision}</span>
1648
- {config.vision_enabled === false && <span style={{ fontSize: 11, opacity: 0.55 }}>{copy.capDisabledNote}</span>}
1649
- </label>
1650
- <label style={S.field}><span style={S.fieldLabel}>{copy.visionProvider}</span>
1651
- <CustomSelect value={config.vision_provider}
1652
- onChange={(p) => {
1653
- const custom = customProviders.find((c) => c.id === p);
1654
- const m = custom ? (custom.models?.[0] ?? 'default') : (VISION_MODELS[p] ?? ['default'])[0];
1655
- setModelAdd(null);
1656
- setConfig((c: any) => ({ ...c, vision_provider: p, vision_model: m }));
1657
- void applyPatch({ vision_provider: p, vision_model: m });
1658
- if ((p === 'grok' || p === 'agy') && !dynModels[p]) {
1659
- void get(`/vision-models?provider=${p}`).then((r) => {
1660
- if (r.ok) setDynModels((d) => ({ ...d, [p]: r.models }));
1661
- }).catch(() => {});
1662
- }
1663
- }}
1664
- options={visionProviderOptions} /></label>
1665
- <label style={S.field}><span style={S.fieldLabel}>{copy.visionModel}</span>
1666
- <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
1667
- {modelAdd === null ? (
1668
- <CustomSelect value={config.vision_model} onChange={(v) => field('vision_model', v)}
1669
- options={visionModelOptions} />
1670
- ) : (
1671
- <input autoFocus style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1672
- value={modelAdd} placeholder={copy.modelPlaceholder}
1673
- onChange={(e) => setModelAdd(e.target.value)}
1674
- onBlur={commitModelAdd}
1675
- onKeyDown={(e: any) => {
1676
- if (e.key === 'Enter') e.currentTarget.blur();
1677
- if (e.key === 'Escape') setModelAdd(null);
1678
- }} />
1679
- )}
1680
- {modelAdd === null && (config.vision_provider === 'grok' || config.vision_provider === 'agy') && (
1681
- <button type="button" title={copy.refreshModels} disabled={busy}
1682
- style={{ ...S.btn, padding: '3px 8px', flexShrink: 0 }}
1683
- onClick={() => {
1684
- const p = config.vision_provider;
1685
- setBusy(true);
1686
- void get(`/vision-models?provider=${p}&refresh=1`)
1687
- .then((r) => { if (r.ok) setDynModels((d) => ({ ...d, [p]: r.models })); })
1688
- .catch(() => {})
1689
- .finally(() => setBusy(false));
1690
- }}>↻</button>
1691
- )}
1692
- {modelAdd === null && config.vision_provider !== 'off' && (
1693
- <button type="button" title={copy.addModelTip} style={{ ...S.btn, padding: '3px 8px', flexShrink: 0 }}
1694
- onClick={() => setModelAdd('')}>+</button>
1695
- )}
1696
- {modelAdd === null && (extraModels[config.vision_provider] ?? []).includes(config.vision_model) && (
1697
- <button type="button" title={copy.removeModelTip} style={{ ...S.btn, padding: '3px 8px', flexShrink: 0 }}
1698
- onClick={removeCurrentModel}>−</button>
1699
- )}
1700
- </div></label>
1701
- <label style={{ ...S.field, flexDirection: 'row' as const, alignItems: 'center', gap: 6, gridColumn: '1 / -1' }} title={copy.capRestartHint}>
1702
- <input type="checkbox" checked={!!config.imagegen_enabled} onChange={(e) => field('imagegen_enabled', e.target.checked)} />
1703
- <span style={{ fontSize: 12.5 }}>{copy.enableImagegen}</span>
1704
- {config.imagegen_enabled === false && <span style={{ fontSize: 11, opacity: 0.55 }}>{copy.capDisabledNote}</span>}
1705
- </label>
1706
- <label style={S.field}><span style={S.fieldLabel}>{copy.imagegenProvider}</span>
1707
- <CustomSelect value={config.imagegen_provider} onChange={(v) => field('imagegen_provider', v)}
1708
- options={imagegenProviderOptions} /></label>
1709
- </>))}
1710
- </CollapsibleSection>
1711
-
1712
1457
  <CollapsibleSection sectionId="harnessProviders" title={copy.sectionNames.harnessProviders}
1713
1458
  summary={sectionSummary(copy.providerCount(providerInventory?.records?.length ?? 0), providerInventoryError ? copy.providerLifecycleError : '')}
1714
1459
  expanded={!!expandedSections.harnessProviders} onToggle={() => toggleSection('harnessProviders')}>
@@ -1818,105 +1563,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
1818
1563
  )}
1819
1564
  </CollapsibleSection>
1820
1565
 
1821
- <CollapsibleSection sectionId="providers" title={copy.sectionNames.providers}
1822
- summary={sectionSummary(copy.providerCount(customProviders.length), testResult?.ok === false && copy.testFail)}
1823
- expanded={!!expandedSections.providers} onToggle={() => toggleSection('providers')}>
1824
- {block(copy.cardCustomProv, (<>
1825
- {customProviders.length === 0 && provForm === null && (
1826
- <div style={{ fontSize: 12, opacity: 0.5 }}>{copy.noCustomProviders}</div>
1827
- )}
1828
- {customProviders.map((p) => (
1829
- <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5 }}>
1830
- <span style={{ fontWeight: 500 }}>{p.name || p.id}</span>
1831
- <span style={{ ...S.chip(false), opacity: 0.7 }}>{provType(p) === 'api' ? 'API' : 'CLI'}</span>
1832
- {canSee(p) && <span style={S.chip(true)}>{copy.capVision}</span>}
1833
- {canDraw(p) && <span style={S.chip(true)}>{copy.capImagegen}</span>}
1834
- {provType(p) === 'api' && !!p.api_key && <span style={{ fontSize: 11.5, opacity: 0.5 }}>key {copy.keySet}</span>}
1835
- <span style={{ flex: 1 }} />
1836
- <button style={{ ...S.btn, padding: '2px 8px' }} title={copy.testTip}
1837
- disabled={!!testResult?.busy}
1838
- onClick={() => runProviderTest(`row:${p.id}`, p)}>
1839
- {testResult?.key === `row:${p.id}` && testResult.busy ? copy.testing : copy.testProvider}</button>
1840
- <button style={{ ...S.btn, padding: '2px 8px' }}
1841
- onClick={() => setProvForm({
1842
- originalId: p.id, name: p.name ?? p.id, type: provType(p),
1843
- models: (p.models ?? []).join(', '),
1844
- base_url: p.base_url ?? '', api_key: p.api_key ?? '', imagegen_model: p.imagegen_model ?? '',
1845
- vision_command: p.vision_command ?? '', imagegen_command: p.imagegen_command ?? '',
1846
- })}>{copy.edit}</button>
1847
- <button style={{ ...S.btn, padding: '2px 8px', opacity: 0.75 }}
1848
- onClick={() => { if (confirm(copy.confirmDeleteProvider(p.name || p.id))) deleteProvider(p.id); }}>{copy.del}</button>
1849
- </div>
1850
- ))}
1851
- {customProviders.some((p) => testResult?.key === `row:${p.id}`) && testReport(testResult!.key)}
1852
- {provForm === null ? (
1853
- <div><button style={S.btn} onClick={() => setProvForm({
1854
- name: '', type: 'api', models: '', base_url: '', api_key: '', imagegen_model: '',
1855
- vision_command: '', imagegen_command: '',
1856
- })}>{copy.addProvider}</button></div>
1857
- ) : (
1858
- <div style={{ borderTop: '1px solid rgba(128,128,128,0.16)', paddingTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }}>
1859
- <div style={S.blockGrid}>
1860
- <label style={S.field}><span style={S.fieldLabel}>{copy.providerName}</span>
1861
- <input autoFocus style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const }}
1862
- value={provForm.name} onChange={(e) => setProvForm({ ...provForm, name: e.target.value })} /></label>
1863
- <label style={S.field}><span style={S.fieldLabel}>{copy.providerType}</span>
1864
- <CustomSelect value={provForm.type} onChange={(v) => setProvForm({ ...provForm, type: v as 'api' | 'cli' })}
1865
- options={[{ value: 'api', label: copy.typeApi }, { value: 'cli', label: copy.typeCli }]} /></label>
1866
- </div>
1867
- {provForm.type === 'api' ? (<>
1868
- <div style={S.blockGrid}>
1869
- <label style={S.field}><span style={S.fieldLabel}>{copy.baseUrl}</span>
1870
- <input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1871
- value={provForm.base_url} placeholder="https://api.example.com/v1"
1872
- onChange={(e) => setProvForm({ ...provForm, base_url: e.target.value })} /></label>
1873
- <label style={S.field}><span style={S.fieldLabel}>{copy.apiKey}</span>
1874
- <input type="password" autoComplete="off" style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1875
- value={provForm.api_key} placeholder={copy.keyPlaceholder}
1876
- onChange={(e) => setProvForm({ ...provForm, api_key: e.target.value })} /></label>
1877
- <label style={S.field}><span style={S.fieldLabel}>{copy.modelsField}</span>
1878
- <input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1879
- value={provForm.models} placeholder="gpt-4o-mini, qwen-vl-max"
1880
- onChange={(e) => setProvForm({ ...provForm, models: e.target.value })} /></label>
1881
- <label style={S.field}><span style={S.fieldLabel}>{copy.imagegenModel}</span>
1882
- <input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1883
- value={provForm.imagegen_model} placeholder="dall-e-3"
1884
- onChange={(e) => setProvForm({ ...provForm, imagegen_model: e.target.value })} /></label>
1885
- </div>
1886
- <div style={{ fontSize: 11.5, opacity: 0.55 }}>{copy.apiFormHint}</div>
1887
- </>) : (<>
1888
- <label style={S.field}><span style={S.fieldLabel}>{copy.visionCmd}</span>
1889
- <input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1890
- value={provForm.vision_command} placeholder="mycli see {image} --ask {question} --model {model}"
1891
- onChange={(e) => setProvForm({ ...provForm, vision_command: e.target.value })} /></label>
1892
- <label style={S.field}><span style={S.fieldLabel}>{copy.imagegenCmd}</span>
1893
- <input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1894
- value={provForm.imagegen_command} placeholder="mycli gen {prompt} -o {output}"
1895
- onChange={(e) => setProvForm({ ...provForm, imagegen_command: e.target.value })} /></label>
1896
- <label style={S.field}><span style={S.fieldLabel}>{copy.modelsField}</span>
1897
- <input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
1898
- value={provForm.models} placeholder="model-a, model-b"
1899
- onChange={(e) => setProvForm({ ...provForm, models: e.target.value })} /></label>
1900
- <div style={{ fontSize: 11.5, opacity: 0.55 }}>{copy.cliFormHint}</div>
1901
- </>)}
1902
- <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
1903
- <button style={S.btn} onClick={saveProviderForm}>{copy.saveProvider}</button>
1904
- <button style={S.btn} title={copy.testTip} disabled={!!testResult?.busy}
1905
- onClick={() => runProviderTest('form', {
1906
- id: provForm.originalId ?? 'draft', name: provForm.name || 'draft', type: provForm.type,
1907
- models: provForm.models.split(/[,,\n]+/).map((x) => x.trim()).filter(Boolean),
1908
- base_url: provForm.base_url.trim(), api_key: provForm.api_key,
1909
- imagegen_model: provForm.imagegen_model.trim(),
1910
- vision_command: provForm.vision_command.trim(), imagegen_command: provForm.imagegen_command.trim(),
1911
- })}>
1912
- {testResult?.key === 'form' && testResult.busy ? copy.testing : copy.testProvider}</button>
1913
- <button style={{ ...S.btn, opacity: 0.75 }} onClick={() => setProvForm(null)}>{copy.cancel}</button>
1914
- </div>
1915
- {testReport('form')}
1916
- </div>
1917
- )}
1918
- </>), false)}
1919
- </CollapsibleSection>
1920
1566
  </>)}
1921
1567
  <div style={{ fontSize: 11.5, opacity: 0.55, marginTop: 2 }}>{copy.globalHint}</div>
1922
1568
 
@@ -3,7 +3,7 @@ import { applyQuick } from './quick-panel';
3
3
  export const inject = ['slots', 'locale'];
4
4
 
5
5
  // Quick-controls entry for the official 3080 surface: master switch,
6
- // flash/pro model priority, vision/imagegen toggles only. Never ships the
6
+ // flash/pro model priority only. Never ships the
7
7
  // full control-plane code.
8
8
  export function apply(ctx: any): void {
9
9
  applyQuick(ctx);