@ran-sh/dsh-crew 1.1.1 → 1.2.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.claude-plugin/marketplace.json +16 -17
  2. package/.claude-plugin/plugin.json +7 -1
  3. package/README.md +44 -15
  4. package/README.zh.md +37 -12
  5. package/agents/ds-flash.md +16 -18
  6. package/agents/ds-pro.md +16 -24
  7. package/agents/ds-reviewer.md +15 -15
  8. package/agents/ds-worker.md +16 -16
  9. package/bin/dsh-crew.mjs +16 -12
  10. package/codex/AGENTS.md +37 -85
  11. package/codex/agents/ds-flash.toml +18 -14
  12. package/codex/agents/ds-pro.toml +18 -15
  13. package/codex/agents/ds-reviewer.toml +17 -13
  14. package/codex/agents/ds-worker.toml +17 -13
  15. package/codex/prompts/dsh-config.md +17 -1
  16. package/codex/prompts/dsh-status.md +5 -1
  17. package/commands/config.md +16 -4
  18. package/commands/status.md +6 -2
  19. package/docs/installation.md +54 -9
  20. package/docs/readiness-matrix.md +14 -6
  21. package/docs/ui-surfaces.md +32 -17
  22. package/lib/client.js +5434 -4844
  23. package/official-web-bridge/lib/client.js +567 -479
  24. package/official-web-bridge/overlay-entry.mjs +59 -0
  25. package/package.json +6 -3
  26. package/scripts/build-client.mjs +2 -0
  27. package/scripts/setup.mjs +8 -7
  28. package/scripts/verify-crew-ui-polish.mjs +145 -0
  29. package/scripts/verify-history-ui.mjs +97 -0
  30. package/scripts/verify-npm-install.mjs +1 -1
  31. package/src/client/history-panel.tsx +108 -0
  32. package/src/client/host-readiness.mjs +15 -6
  33. package/src/client/index.tsx +86 -64
  34. package/src/client/model-callability-view.mjs +17 -0
  35. package/src/client/panel-chrome.tsx +40 -0
  36. package/src/client/quick-panel.tsx +88 -73
  37. package/src/client/readiness-envelope.mjs +34 -0
  38. package/src/config-readiness.mjs +181 -115
  39. package/src/extension-contract.mjs +44 -26
  40. package/src/history/admission-gate.mjs +51 -0
  41. package/src/history/archive-store.mjs +261 -0
  42. package/src/history/cleanup-plan.mjs +64 -0
  43. package/src/history/http.mjs +32 -0
  44. package/src/history/operation.mjs +86 -0
  45. package/src/history/runner.mjs +51 -0
  46. package/src/history/runtime.mjs +28 -0
  47. package/src/history/service.mjs +137 -0
  48. package/src/history/state.mjs +27 -0
  49. package/src/hub/index.mjs +141 -24
  50. package/src/hub-client.mjs +17 -15
  51. package/src/image-output.mjs +71 -0
  52. package/src/install/install-legacy.mjs +126 -23
  53. package/src/install/npx-lifecycle.mjs +373 -64
  54. package/src/install/official-frontend-assets.mjs +78 -0
  55. package/src/install/payload-content.mjs +88 -0
  56. package/src/install/windows-startup.mjs +107 -19
  57. package/src/install/windows-supervisor-adapter.mjs +443 -0
  58. package/src/install/windows-supervisor-lifecycle.mjs +782 -0
  59. package/src/install/zcode.mjs +53 -14
  60. package/src/job-contracts.mjs +37 -31
  61. package/src/jobs.mjs +2 -2
  62. package/src/mcp-runtime.mjs +74 -9
  63. package/src/model-callability-contract.mjs +79 -0
  64. package/src/multimodal.mjs +0 -0
  65. package/src/official-web-bridge.mjs +0 -2
  66. package/src/provider-health.mjs +130 -124
  67. package/src/readiness-matrix.mjs +20 -15
  68. package/src/runtime-identity-contract.mjs +34 -0
  69. package/src/runtime-identity.mjs +14 -4
  70. package/src/runtime-readiness-snapshot.mjs +285 -87
  71. package/src/server.mjs +86 -76
  72. package/src/standalone-sdk.mjs +23 -0
  73. package/src/supervisor/restart-request.mjs +64 -10
  74. package/src/workflow-runtime.mjs +28 -13
  75. package/windows/start-dsh-crew.cmd +57 -57
  76. package/windows/start-dsh-crew.ps1 +314 -51
  77. package/windows/supervisor-control.ps1 +467 -0
  78. package/zcode/AGENTS.md +4 -1
  79. package/zcode/agents/ds-reviewer.md +31 -36
  80. package/zcode/agents/ds-worker.md +31 -39
  81. package/zcode/commands/dsh-config.md +17 -6
  82. package/zcode/commands/dsh-status.md +5 -4
  83. package/.mcp.json +0 -8
@@ -1,12 +1,12 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
+ import { PanelHeader, PanelStyles } from './panel-chrome';
2
3
 
3
4
  // DSH Crew QUICK CONTROLS panel for the official 3080 surface.
4
5
  //
5
6
  // Narrow by design: only the user-facing master switch, flash/pro model
6
7
  // priority lists and vision/imagegen toggles — all other operations belong
7
8
  // to the native 3210 full control plane. Talks only to the quick endpoints
8
- // (/_dsh/dsh-crew/quick-config, /quick-status, /runtime/restart-request,
9
- // /runtime/restart-status). NEVER writes to any other surface.
9
+ // (/_dsh/dsh-crew/quick-config, /quick-status). Runtime maintenance stays on 3210.
10
10
 
11
11
  const API = '/_dsh/dsh-crew';
12
12
  const CREW_CONTROL_PLANE_URL = 'http://127.0.0.1:3210/';
@@ -33,14 +33,20 @@ const RESTART_KEYS = new Set([
33
33
  const T = {
34
34
  zh: {
35
35
  title: 'DSH Crew 快捷控制',
36
- openFull: '打开完整设置 →',
37
- running: '运行中',
36
+ openFull: '打开 3210 后台 →',
37
+ running: '后台已连接',
38
+ surface: '3080 / 日常控制',
39
+ description: '常用开关与模型顺序在这里调整,更多设置进入后台。',
40
+ priorityHint: '从上到下按优先级排列;从列表移除不会删除 Provider。',
41
+ empty: '未配置优先模型,按现有后备策略选择。',
42
+ models: (n: number) => `${n} 个模型`,
43
+ moveUp: '上移', moveDown: '下移', remove: '从优先级移除',
38
44
  unavailable: 'Crew 后端不可用',
39
45
  openDiag: '打开诊断',
40
46
  crew: 'Crew',
41
47
  enabled: '启用子 Agent',
42
- flash: 'Flash 模型',
43
- pro: 'Pro 模型',
48
+ flash: 'Worker / Flash',
49
+ pro: 'Reviewer / Pro',
44
50
  addModel: '+ 添加模型',
45
51
  multimodal: '多模态',
46
52
  vision: '视觉',
@@ -55,14 +61,20 @@ const T = {
55
61
  },
56
62
  en: {
57
63
  title: 'DSH Crew Quick Controls',
58
- openFull: 'Open full settings →',
59
- running: 'Running',
64
+ openFull: 'Open Crew backend (3210) →',
65
+ running: 'Backend connected',
66
+ surface: '3080 / DAILY CONTROLS',
67
+ description: 'Manage everyday switches and model order here. Open the backend for advanced settings.',
68
+ priorityHint: 'Ordered by priority. Removing a model here does not delete its provider.',
69
+ empty: 'No priority models. The existing fallback policy applies.',
70
+ models: (n: number) => `${n} models`,
71
+ moveUp: 'Move up', moveDown: 'Move down', remove: 'Remove from priority',
60
72
  unavailable: 'Crew backend unavailable',
61
73
  openDiag: 'Open diagnostics',
62
74
  crew: 'Crew',
63
75
  enabled: 'Enable sub-agents',
64
- flash: 'Flash models',
65
- pro: 'Pro models',
76
+ flash: 'Worker / Flash',
77
+ pro: 'Reviewer / Pro',
66
78
  addModel: '+ Add model',
67
79
  multimodal: 'Multimodal',
68
80
  vision: 'Vision',
@@ -73,23 +85,39 @@ const T = {
73
85
  saved: 'Saved',
74
86
  working: 'Working…',
75
87
  providerPlaceholder: 'provider',
76
- modelPlaceholder: 'model',
88
+ modelPlaceholder: 'Model',
77
89
  },
78
90
  };
79
91
 
80
92
  function LocalStyles() {
81
93
  return (
82
94
  <style>{`
83
- .crew-quick-card { border: 1px solid rgba(128,128,128,0.24); border-radius: 12px; padding: 14px 15px; font-size: 13px; line-height: 1.55; display: flex; flex-direction: column; gap: 10px; background: linear-gradient(135deg, rgba(74,158,255,0.10), rgba(128,128,128,0.025) 56%); }
95
+ .dsh-crew-ui.crew-quick-card { display: flex; flex-direction: column; gap: 12px; }
84
96
  .crew-quick-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
85
97
  .crew-quick-title { font-size: 16px; font-weight: 680; }
86
98
  .crew-quick-section { font-weight: 650; opacity: 0.85; }
87
99
  .crew-quick-chip { border: 1px solid rgba(128,128,128,0.35); border-radius: 999px; padding: 1px 10px; font-size: 12px; }
88
- .crew-quick-btn { border: 1px solid rgba(128,128,128,0.4); border-radius: 8px; padding: 2px 10px; cursor: pointer; background: transparent; font-size: 12.5px; }
100
+ .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; }
89
101
  .crew-quick-btn.primary { border-color: #4a9eff; color: #4a9eff; font-weight: 650; }
90
- .crew-quick-input { border: 1px solid rgba(128,128,128,0.35); border-radius: 6px; padding: 2px 8px; font-size: 12.5px; width: 130px; background: transparent; color: inherit; }
102
+ .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; }
91
103
  .crew-quick-notice { opacity: 0.75; font-size: 12.5px; }
92
- .crew-quick-model { display: flex; gap: 6px; align-items: center; font-size: 12.5px; }
104
+ .crew-quick-card .crew-quick-master { padding: 11px 14px; border: 1px solid var(--crew-line); border-radius: 10px; justify-content: space-between; }
105
+ .crew-quick-card input[type=checkbox] { accent-color: var(--crew-accent); width: 15px; height: 15px; }
106
+ .crew-quick-card label { display: inline-flex; gap: 7px; align-items: center; }
107
+ .crew-quick-card .crew-quick-group { border: 1px solid var(--crew-line); border-radius: 10px; overflow: hidden; }
108
+ .crew-quick-card .crew-quick-group > summary { cursor: pointer; padding: 12px 14px; }
109
+ .crew-quick-card .crew-quick-count { margin-left: 10px; font-size: 11px; opacity: .65; font-weight: 400; }
110
+ .crew-quick-card .crew-quick-content { padding: 0 14px 14px; }
111
+ .crew-quick-card .crew-quick-list { padding: 0; margin: 10px 0; list-style: none; }
112
+ .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; }
113
+ .crew-quick-card .crew-quick-rank { opacity: .5; font-size: 11px; font-variant-numeric: tabular-nums; }
114
+ .crew-quick-card .crew-quick-model-name { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
115
+ .crew-quick-card .crew-quick-provider { font-size: 11px; opacity: .65; overflow-wrap: anywhere; }
116
+ .crew-quick-card .crew-quick-actions { display: flex; gap: 4px; }
117
+ .crew-quick-card .crew-quick-add > summary { color: var(--crew-accent); cursor: pointer; font-size: 12px; padding: 4px 0; }
118
+ .crew-quick-card .crew-quick-form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: 7px; margin-top: 9px; }
119
+ .crew-quick-card .crew-quick-media { display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 10px; align-items: center; margin-top: 10px; }
120
+ @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; } }
93
121
  `}</style>
94
122
  );
95
123
  }
@@ -174,108 +202,95 @@ export function QuickPanel({ ctx }: { ctx: any }) {
174
202
  void patch({ [listKey]: list } as unknown as QuickConfig);
175
203
  };
176
204
 
177
- const applyRestart = useCallback(async () => {
178
- setBusy(true); setNotice(t.working);
179
- try {
180
- const created = await readJson(await fetch(`${API}/runtime/restart-request`, {
181
- method: 'POST', headers: { 'content-type': 'application/json' },
182
- body: JSON.stringify({ confirm: true, reason: 'quick panel restart' }),
183
- }));
184
- if (!created.ok) throw new Error(created.code ?? created.error ?? 'restart failed');
185
- const requestId = created.request_id;
186
- const deadline = Date.now() + 90_000;
187
- for (;;) {
188
- await new Promise((r) => setTimeout(r, 1000));
189
- if (Date.now() > deadline) throw new Error('restart timed out');
190
- const status = await readJson(await fetch(`${API}/runtime/restart-status?id=${encodeURIComponent(requestId)}`, { cache: 'no-store' }));
191
- if (!status.ok) continue;
192
- if (status.state === 'VERIFIED') { setRestartPending(false); setNotice(t.saved); return; }
193
- if (status.state !== 'RESTART_REQUESTED') throw new Error(status.state ?? 'restart failed');
194
- }
195
- } catch (e: any) {
196
- setNotice(String(e?.message ?? e));
197
- } finally { setBusy(false); }
198
- }, [t]);
199
-
200
205
  const modelList = (listKey: 'flash_model_priority' | 'pro_model_priority', label: string) => {
201
206
  const list: ModelEntry[] = (config?.[listKey] as ModelEntry[] | undefined) ?? [];
202
207
  const draft = drafts[listKey] ?? { provider: '', model: '' };
203
208
  return (
204
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
205
- <div className="crew-quick-section">{label}</div>
209
+ <details className="crew-quick-group" open={listKey === 'flash_model_priority'}>
210
+ <summary><strong>{label}</strong><span className="crew-quick-count">{t.models(list.length)}</span></summary>
211
+ <div className="crew-quick-content">
212
+ <div className="crew-quick-notice">{t.priorityHint}</div>
213
+ {list.length === 0 && <p className="crew-quick-notice">{t.empty}</p>}
214
+ <ol className="crew-quick-list" aria-label={label}>
206
215
  {list.map((entry, i) => (
207
- <div key={`${entry.provider}/${entry.model}/${i}`} className="crew-quick-model">
208
- <span>{i + 1}. {entry.provider} / {entry.model}</span>
209
- <button className="crew-quick-btn" disabled={busy || i === 0} onClick={() => moveModel(listKey, i, -1)}>↑</button>
210
- <button className="crew-quick-btn" disabled={busy || i === list.length - 1} onClick={() => moveModel(listKey, i, 1)}>↓</button>
211
- <button className="crew-quick-btn" disabled={busy} onClick={() => removeModel(listKey, i)}>×</button>
212
- </div>
216
+ <li key={`${entry.provider}/${entry.model}/${i}`} className="crew-quick-model">
217
+ <span className="crew-quick-rank" aria-hidden="true">{i + 1}.</span>
218
+ <div><div className="crew-quick-model-name">{entry.model}</div><div className="crew-quick-provider">{entry.provider}</div></div>
219
+ <div className="crew-quick-actions">
220
+ <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>
221
+ <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>
222
+ <button type="button" className="crew-quick-btn" aria-label={`${t.remove} ${entry.model}`} title={t.remove} disabled={busy} onClick={() => removeModel(listKey, i)}>×</button>
223
+ </div>
224
+ </li>
213
225
  ))}
214
- <div className="crew-quick-row">
215
- <input className="crew-quick-input" placeholder={t.providerPlaceholder} disabled={busy}
226
+ </ol>
227
+ <details className="crew-quick-add">
228
+ <summary>{t.addModel}</summary>
229
+ <div className="crew-quick-form">
230
+ <input className="crew-quick-input" aria-label={`${label} Provider`} placeholder={t.providerPlaceholder} disabled={busy}
216
231
  value={draft.provider} onChange={(e) => setDrafts((d) => ({ ...d, [listKey]: { ...draft, provider: e.target.value } }))} />
217
- <input className="crew-quick-input" placeholder={t.modelPlaceholder} disabled={busy}
232
+ <input className="crew-quick-input" aria-label={`${label} ${locale === 'zh' ? '模型' : 'Model'}`} placeholder={t.modelPlaceholder} disabled={busy}
218
233
  value={draft.model} onChange={(e) => setDrafts((d) => ({ ...d, [listKey]: { ...draft, model: e.target.value } }))} />
219
- <button className="crew-quick-btn" disabled={busy} onClick={() => addModel(listKey)}>{t.addModel}</button>
234
+ <button type="button" className="crew-quick-btn" disabled={busy || !draft.provider.trim() || !draft.model.trim()} onClick={() => addModel(listKey)}>{t.addModel}</button>
220
235
  </div>
221
- </div>
236
+ </details>
237
+ </div>
238
+ </details>
222
239
  );
223
240
  };
224
241
 
225
242
  if (ready === false) {
226
243
  return (
227
- <div className="crew-quick-card">
228
- <LocalStyles />
244
+ <div className="dsh-crew-ui crew-quick-card">
245
+ <PanelStyles /><LocalStyles />
229
246
  <div className="crew-quick-title">{t.title}</div>
230
247
  <div className="crew-quick-notice">{t.unavailable}</div>
231
248
  <div className="crew-quick-row">
232
- <a href={FULL} target="_blank" rel="noreferrer">{t.openDiag}</a>
249
+ <a className="crew-nav-link" href={FULL} target="_blank" rel="noopener noreferrer">{t.openDiag}</a>
233
250
  </div>
234
251
  </div>
235
252
  );
236
253
  }
237
254
  if (config === null) {
238
- return (<div className="crew-quick-card"><LocalStyles /><div className="crew-quick-notice">{t.working}</div></div>);
255
+ return (<div className="dsh-crew-ui crew-quick-card"><PanelStyles /><LocalStyles /><div className="crew-quick-notice" role="status">{t.working}</div></div>);
239
256
  }
240
257
 
241
258
  return (
242
- <div className="crew-quick-card">
243
- <LocalStyles />
244
- <div className="crew-quick-row" style={{ justifyContent: 'space-between' }}>
245
- <span className="crew-quick-title">{t.title}</span>
259
+ <div className="dsh-crew-ui crew-quick-card">
260
+ <PanelStyles /><LocalStyles />
261
+ <PanelHeader title={t.title} eyebrow={t.surface} description={t.description} href={FULL} linkText={t.openFull}>
246
262
  <span className="crew-quick-chip">{t.running} · 3210</span>
247
- <a href={FULL} target="_blank" rel="noreferrer">{t.openFull}</a>
248
- </div>
249
- <div className="crew-quick-row">
263
+ </PanelHeader>
264
+ <div className="crew-quick-row crew-quick-master">
250
265
  <span className="crew-quick-section">{t.crew}</span>
251
266
  <label><input type="checkbox" disabled={busy} checked={config.subagents_enabled !== false}
252
267
  onChange={(e) => toggle('subagents_enabled', e.target.checked)} /> {t.enabled}</label>
253
268
  </div>
254
269
  {modelList('flash_model_priority', t.flash)}
255
270
  {modelList('pro_model_priority', t.pro)}
256
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
257
- <div className="crew-quick-section">{t.multimodal}</div>
258
- <div className="crew-quick-row">
271
+ <details className="crew-quick-group">
272
+ <summary><strong>{t.multimodal}</strong><span className="crew-quick-count">{t.vision} / {t.imagegen}</span></summary>
273
+ <div className="crew-quick-content">
274
+ <div className="crew-quick-media">
259
275
  <label><input type="checkbox" disabled={busy} checked={config.vision_enabled === true}
260
276
  onChange={(e) => toggle('vision_enabled', e.target.checked)} /> {t.vision}</label>
261
- <span>{t.provider}</span>
262
- <input className="crew-quick-input" disabled={busy} defaultValue={config.vision_provider ?? ''} key={`vision-${config.vision_provider ?? ''}`}
277
+ <input className="crew-quick-input" aria-label={`${t.vision} Provider`} placeholder={t.provider} disabled={busy} defaultValue={config.vision_provider ?? ''} key={`vision-${config.vision_provider ?? ''}`}
263
278
  onBlur={(e) => setProvider('vision_provider', e.target.value)} />
264
279
  </div>
265
- <div className="crew-quick-row">
280
+ <div className="crew-quick-media">
266
281
  <label><input type="checkbox" disabled={busy} checked={config.imagegen_enabled === true}
267
282
  onChange={(e) => toggle('imagegen_enabled', e.target.checked)} /> {t.imagegen}</label>
268
- <span>{t.provider}</span>
269
- <input className="crew-quick-input" disabled={busy} defaultValue={config.imagegen_provider ?? ''}
283
+ <input className="crew-quick-input" aria-label={`${t.imagegen} Provider`} placeholder={t.provider} disabled={busy} defaultValue={config.imagegen_provider ?? ''}
270
284
  onBlur={(e) => setProvider('imagegen_provider', e.target.value)} />
271
285
  </div>
272
- </div>
286
+ </div>
287
+ </details>
273
288
  {restartPending && (
274
289
  <div className="crew-quick-row">
275
- <button className="crew-quick-btn primary" disabled={busy} onClick={() => void applyRestart()}>{t.applyRestart}</button>
290
+ <a className="crew-quick-btn primary" href={FULL} target="_blank" rel="noopener noreferrer">{t.openFull}</a>
276
291
  </div>
277
292
  )}
278
- {notice && <div className="crew-quick-notice">{notice}</div>}
293
+ {notice && <div className="crew-quick-notice" role="status" aria-live="polite">{notice}</div>}
279
294
  </div>
280
295
  );
281
296
  }
@@ -0,0 +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,145 +1,211 @@
1
- import { buildReadinessMatrix, READINESS_REASON_CODES } from './readiness-matrix.mjs';
2
- import { PRODUCTION_EXECUTION_PLANE, PRODUCTION_PROFILE, PRODUCTION_LISTEN_PORT } from './runtime-identity.mjs';
1
+ import { buildReadinessMatrix, READINESS_REASON_CODES } from './readiness-matrix.mjs';
2
+ import { PRODUCTION_EXECUTION_PLANE, PRODUCTION_PROFILE, PRODUCTION_LISTEN_PORT } from './runtime-identity.mjs';
3
3
 
4
- function warningCodes(catalogBody) {
4
+ function warningCodes(catalogBody) {
5
5
  const hints = Array.isArray(catalogBody?.health?.hints) ? catalogBody.health.hints : [];
6
6
  return [...new Set(hints
7
7
  .filter((hint) => hint?.level === 'warning' && typeof hint?.code === 'string' && hint.code.trim())
8
8
  .map((hint) => hint.code.trim()))];
9
- }
10
-
11
- function verifiedHubJob(job) {
12
- const context = job?.execution_context;
13
- return job?.status === 'done'
14
- && job?.task_status === 'success'
15
- && job?.delivery_complete === true
16
- && job?.workspace_evidence_ok === true
17
- && context?.execution_plane === PRODUCTION_EXECUTION_PLANE
18
- && context?.profile === PRODUCTION_PROFILE
19
- && Number(context?.listen_port) === PRODUCTION_LISTEN_PORT
20
- && typeof context?.runtime_id === 'string'
21
- && context.runtime_id.trim().length > 0;
22
- }
23
-
24
- function structurallyConsistentProviderInventory(body) {
25
- if (!body || typeof body !== 'object' || body.ok === false || !Array.isArray(body.records)) return false;
26
- return body.records.every((record) => {
27
- if (!record || typeof record.id !== 'string' || record.id.trim() === '') return false;
28
- if (!['present', 'absent'].includes(record.desired_state)) return false;
29
- if (!record.lifecycle || typeof record.lifecycle !== 'object' || Array.isArray(record.lifecycle)) return false;
30
- for (const field of ['installed', 'configured', 'enabled', 'catalogued']) {
31
- if (typeof record.lifecycle[field] !== 'boolean') return false;
32
- }
33
- if (record.desired_state === 'absent' && record.lifecycle.enabled === true) return false;
34
- return true;
35
- });
36
- }
37
-
38
- export function buildHubExecutionRows(hubJobs = []) {
39
- const jobs = Array.isArray(hubJobs) ? hubJobs : [];
40
- const workerPassed = jobs.some((job) => job?.role === 'worker' && verifiedHubJob(job));
41
- const workerPrimaryPassed = jobs.some((job) => job?.role === 'worker'
42
- && verifiedHubJob(job)
43
- && Number(job?.attempt ?? job?.selection_trace?.logical_attempt ?? 0) === 0);
44
- const workerEscalationPassed = jobs.some((job) => job?.role === 'worker'
45
- && verifiedHubJob(job)
46
- && (Number(job?.attempt ?? job?.selection_trace?.logical_attempt ?? 0) > 0));
47
- const reviewerPassed = jobs.some((job) => job?.role === 'reviewer'
48
- && verifiedHubJob(job)
49
- && job?.review_verdict === 'approve');
50
- return [
51
- workerPassed
52
- ? { id: 'model_execution', status: 'PASS', reason_code: 'REAL_EXECUTION_PASSED', evidence_source: 'hub-jobs' }
53
- : { id: 'model_execution', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
54
- workerPrimaryPassed
55
- ? { id: 'worker_primary_callable', status: 'PASS', reason_code: 'WORKER_PRIMARY_CALLABLE', evidence_source: 'hub-jobs' }
56
- : { id: 'worker_primary_callable', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
57
- workerEscalationPassed
58
- ? { id: 'worker_escalation_callable', status: 'PASS', reason_code: 'WORKER_ESCALATION_CALLABLE', evidence_source: 'hub-jobs' }
59
- : { id: 'worker_escalation_callable', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
60
- reviewerPassed
61
- ? { id: 'reviewer_primary_callable', status: 'PASS', reason_code: 'REVIEWER_PRIMARY_CALLABLE', evidence_source: 'hub-jobs' }
62
- : { id: 'reviewer_primary_callable', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
63
- { id: 'provider_lifecycle_consistent', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
64
- reviewerPassed
65
- ? { id: 'reviewer_pipeline', status: 'PASS', reason_code: 'REAL_REVIEW_PASSED', evidence_source: 'hub-jobs' }
66
- : { id: 'reviewer_pipeline', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
67
- ];
68
- }
9
+ }
10
+
11
+ function verifiedHubJob(job) {
12
+ const context = job?.execution_context;
13
+ return job?.status === 'done'
14
+ && job?.task_status === 'success'
15
+ && job?.delivery_complete === true
16
+ && job?.workspace_evidence_ok === true
17
+ && context?.execution_plane === PRODUCTION_EXECUTION_PLANE
18
+ && context?.profile === PRODUCTION_PROFILE
19
+ && Number(context?.listen_port) === PRODUCTION_LISTEN_PORT
20
+ && typeof context?.runtime_id === 'string'
21
+ && context.runtime_id.trim().length > 0;
22
+ }
23
+
24
+ function hasBlockingProviderRecovery(body, currentSelections) {
25
+ if (!Object.hasOwn(body, 'recovery_transactions')) return false;
26
+ if (!Array.isArray(body.recovery_transactions)) return true;
27
+ const selectedProviders = new Set(Object.values(currentSelections ?? {})
28
+ .filter((selection) => selection?.ok === true && typeof selection.provider === 'string')
29
+ .map((selection) => selection.provider));
30
+ return body.recovery_transactions.some((transaction) => transaction?.unresolved === true
31
+ || typeof transaction?.provider_id !== 'string'
32
+ || selectedProviders.has(transaction.provider_id));
33
+ }
34
+
35
+ function structurallyConsistentProviderInventory(body, currentSelections) {
36
+ if (!body || typeof body !== 'object' || body.ok === false || !Array.isArray(body.records)) return false;
37
+ if ('lifecycle_evidence' in body && body.lifecycle_evidence?.ok !== true) return false;
38
+ if ('declaration_evidence' in body && body.declaration_evidence?.ok !== true) return false;
39
+ if ('default_evidence' in body && body.default_evidence?.ok !== true) return false;
40
+ if (hasBlockingProviderRecovery(body, currentSelections)) return false;
41
+ return body.records.every((record) => {
42
+ if (!record || typeof record.id !== 'string' || record.id.trim() === '') return false;
43
+ if (!['present', 'absent'].includes(record.desired_state)) return false;
44
+ if (!record.lifecycle || typeof record.lifecycle !== 'object' || Array.isArray(record.lifecycle)) return false;
45
+ for (const field of ['installed', 'configured', 'enabled', 'catalogued']) {
46
+ if (typeof record.lifecycle[field] !== 'boolean') return false;
47
+ }
48
+ if (record.desired_state === 'absent' && record.lifecycle.enabled === true) return false;
49
+ return true;
50
+ });
51
+ }
52
+
53
+ function matchesCurrentRoute(job, currentSelections, role) {
54
+ if (!currentSelections) return true;
55
+ const selectionKey = role === 'worker'
56
+ && Number(job?.attempt ?? job?.selection_trace?.logical_attempt ?? 0) > 0
57
+ && Object.hasOwn(currentSelections, 'worker_escalation')
58
+ ? 'worker_escalation'
59
+ : role;
60
+ if (!Object.hasOwn(currentSelections, selectionKey)) return true;
61
+ const selected = currentSelections[selectionKey];
62
+ return selected?.ok === true
63
+ && job?.provider === selected.provider
64
+ && job?.model === selected.model;
65
+ }
66
+
67
+ function safeDetailCode(value, fallback) {
68
+ return typeof value === 'string' && /^[A-Z][A-Z0-9_]{0,127}$/u.test(value) ? value : fallback;
69
+ }
70
+
71
+ function currentRouteHealthEvidence({ role, currentSelections, providerHealthChecked, providerHealthBody } = {}) {
72
+ if (!currentSelections || !Object.hasOwn(currentSelections, role)) return null;
73
+ const selected = currentSelections[role];
74
+ if (selected?.ok === false) {
75
+ return {
76
+ status: 'FAIL',
77
+ reason_code: 'PROVIDER_ROUTE_UNCALLABLE',
78
+ evidence_source: 'model-routing',
79
+ detail_code: safeDetailCode(selected.code, 'MODEL_ROUTE_UNAVAILABLE'),
80
+ };
81
+ }
82
+ if (!selected?.provider || !selected?.model || providerHealthChecked !== true) return null;
83
+ if (providerHealthBody?.ok === false) {
84
+ return { status: 'FAIL', reason_code: 'PROVIDER_HEALTH_UNAVAILABLE', evidence_source: 'provider-health' };
85
+ }
86
+ const health = Array.isArray(providerHealthBody?.health) ? providerHealthBody.health : [];
87
+ const observation = health.find((entry) => entry?.provider === selected.provider
88
+ && entry?.model === selected.model
89
+ && entry?.fresh === true);
90
+ if (!observation || observation.state === 'unprobed') return null;
91
+ if (observation.state === 'callable') {
92
+ return { status: 'PASS', reason_code: 'PROVIDER_ROUTE_CALLABLE', evidence_source: 'provider-health' };
93
+ }
94
+ const stateCode = String(observation.state ?? '').toUpperCase().replaceAll('-', '_');
95
+ return {
96
+ status: 'FAIL',
97
+ reason_code: 'PROVIDER_ROUTE_UNCALLABLE',
98
+ evidence_source: 'provider-health',
99
+ detail_code: safeDetailCode(observation.reason_code, safeDetailCode(stateCode, 'PROVIDER_UNCALLABLE')),
100
+ };
101
+ }
102
+
103
+ export function buildHubExecutionRows(hubJobs = [], { currentSelections = null } = {}) {
104
+ const jobs = Array.isArray(hubJobs) ? hubJobs : [];
105
+ const workerPassed = jobs.some((job) => job?.role === 'worker' && matchesCurrentRoute(job, currentSelections, 'worker') && verifiedHubJob(job));
106
+ const workerPrimaryPassed = jobs.some((job) => job?.role === 'worker'
107
+ && matchesCurrentRoute(job, currentSelections, 'worker')
108
+ && verifiedHubJob(job)
109
+ && Number(job?.attempt ?? job?.selection_trace?.logical_attempt ?? 0) === 0);
110
+ const workerEscalationPassed = jobs.some((job) => job?.role === 'worker'
111
+ && matchesCurrentRoute(job, currentSelections, 'worker')
112
+ && verifiedHubJob(job)
113
+ && (Number(job?.attempt ?? job?.selection_trace?.logical_attempt ?? 0) > 0));
114
+ const reviewerPassed = jobs.some((job) => job?.role === 'reviewer'
115
+ && matchesCurrentRoute(job, currentSelections, 'reviewer')
116
+ && verifiedHubJob(job)
117
+ && job?.review_verdict === 'approve');
118
+ return [
119
+ workerPassed
120
+ ? { id: 'model_execution', status: 'PASS', reason_code: 'REAL_EXECUTION_PASSED', evidence_source: 'hub-jobs' }
121
+ : { id: 'model_execution', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
122
+ workerPrimaryPassed
123
+ ? { id: 'worker_primary_callable', status: 'PASS', reason_code: 'WORKER_PRIMARY_CALLABLE', evidence_source: 'hub-jobs' }
124
+ : { id: 'worker_primary_callable', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
125
+ workerEscalationPassed
126
+ ? { id: 'worker_escalation_callable', status: 'PASS', reason_code: 'WORKER_ESCALATION_CALLABLE', evidence_source: 'hub-jobs' }
127
+ : { id: 'worker_escalation_callable', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
128
+ reviewerPassed
129
+ ? { id: 'reviewer_primary_callable', status: 'PASS', reason_code: 'REVIEWER_PRIMARY_CALLABLE', evidence_source: 'hub-jobs' }
130
+ : { id: 'reviewer_primary_callable', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
131
+ { id: 'provider_lifecycle_consistent', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
132
+ reviewerPassed
133
+ ? { id: 'reviewer_pipeline', status: 'PASS', reason_code: 'REAL_REVIEW_PASSED', evidence_source: 'hub-jobs' }
134
+ : { id: 'reviewer_pipeline', status: 'NOT_RUN', reason_code: 'NO_EXECUTION_EVIDENCE', evidence_source: 'none' },
135
+ ];
136
+ }
69
137
 
70
138
  /**
71
139
  * Enrich the conservative runtime matrix with evidence the config report has
72
140
  * already collected. This function performs no I/O and never reads provider
73
141
  * configuration, credentials, quotas, pricing, or hidden catalog expectations.
74
142
  */
75
- export function buildConfigReadinessMatrix({
76
- platform = process.platform,
77
- hubCompatibility = null,
78
- workerProviderMode = null,
79
- providerCatalogChecked = false,
80
- providerCatalogBody = null,
81
- providerInventoryChecked = false,
82
- providerInventoryBody = null,
83
- providerHealthChecked = false,
84
- providerHealthBody = null,
85
- hubJobsChecked = false,
86
- hubJobsBody = null,
87
- } = {}) {
143
+ export function buildConfigReadinessMatrix({
144
+ platform = process.platform,
145
+ hubCompatibility = null,
146
+ workerProviderMode = null,
147
+ providerCatalogChecked = false,
148
+ providerCatalogBody = null,
149
+ providerInventoryChecked = false,
150
+ providerInventoryBody = null,
151
+ providerHealthChecked = false,
152
+ providerHealthBody = null,
153
+ hubJobsChecked = false,
154
+ hubJobsBody = null,
155
+ currentSelections = null,
156
+ } = {}) {
88
157
  const warnings = warningCodes(providerCatalogBody);
89
158
  const catalogResponseOk = !!providerCatalogBody
90
159
  && typeof providerCatalogBody === 'object'
91
160
  && providerCatalogBody.ok !== false;
92
161
  const catalogOk = providerCatalogChecked && catalogResponseOk && warnings.length === 0;
93
162
 
94
- const evidence = {};
95
- const hubJobs = hubCompatibility?.compatible === true
96
- && hubJobsChecked
97
- && hubJobsBody?.ok !== false
98
- && Array.isArray(hubJobsBody?.jobs)
99
- ? hubJobsBody.jobs
100
- : [];
101
- for (const row of buildHubExecutionRows(hubJobs)) {
102
- if (row.status === 'PASS') {
103
- evidence[row.id] = {
104
- status: row.status,
105
- reason_code: row.reason_code,
106
- evidence_source: row.evidence_source,
107
- };
108
- }
109
- }
110
- if (
163
+ const evidence = {};
164
+ const hubJobs = hubCompatibility?.compatible === true
165
+ && hubJobsChecked
166
+ && hubJobsBody?.ok !== false
167
+ && Array.isArray(hubJobsBody?.jobs)
168
+ ? hubJobsBody.jobs
169
+ : [];
170
+ for (const row of buildHubExecutionRows(hubJobs, { currentSelections })) {
171
+ if (row.status === 'PASS') {
172
+ evidence[row.id] = {
173
+ status: row.status,
174
+ reason_code: row.reason_code,
175
+ evidence_source: row.evidence_source,
176
+ };
177
+ }
178
+ }
179
+ if (
111
180
  workerProviderMode !== 'deepseek-official'
112
181
  && hubCompatibility?.compatible === true
113
182
  && providerCatalogChecked
114
183
  && catalogResponseOk
115
184
  && warnings.length > 0
116
- ) {
185
+ ) {
117
186
  evidence.provider_catalog = {
118
187
  status: 'FAIL',
119
188
  reason_code: READINESS_REASON_CODES.PROVIDER_CATALOG_HEALTH_WARNING,
120
189
  evidence_source: 'harness-catalog',
121
190
  };
122
- }
123
-
124
- if (providerInventoryChecked) {
125
- evidence.provider_lifecycle_consistent = structurallyConsistentProviderInventory(providerInventoryBody)
126
- ? { status: 'PASS', reason_code: READINESS_REASON_CODES.PROVIDER_LIFECYCLE_CONSISTENT, evidence_source: 'provider-inventory' }
127
- : {
128
- status: 'FAIL',
129
- reason_code: providerInventoryBody?.ok === false
130
- ? READINESS_REASON_CODES.PROVIDER_INVENTORY_UNAVAILABLE
131
- : READINESS_REASON_CODES.PROVIDER_LIFECYCLE_INCONSISTENT,
132
- evidence_source: 'provider-inventory',
133
- };
134
- }
135
-
136
- if (providerHealthChecked && providerHealthBody?.ok === false) {
137
- evidence.provider_health = {
138
- status: 'FAIL',
139
- reason_code: 'PROVIDER_HEALTH_UNAVAILABLE',
140
- evidence_source: 'provider-health',
141
- };
142
- }
191
+ }
192
+
193
+ if (providerInventoryChecked) {
194
+ evidence.provider_lifecycle_consistent = structurallyConsistentProviderInventory(providerInventoryBody, currentSelections)
195
+ ? { status: 'PASS', reason_code: READINESS_REASON_CODES.PROVIDER_LIFECYCLE_CONSISTENT, evidence_source: 'provider-inventory' }
196
+ : {
197
+ status: 'FAIL',
198
+ reason_code: providerInventoryBody?.ok === false
199
+ ? READINESS_REASON_CODES.PROVIDER_INVENTORY_UNAVAILABLE
200
+ : READINESS_REASON_CODES.PROVIDER_LIFECYCLE_INCONSISTENT,
201
+ evidence_source: 'provider-inventory',
202
+ };
203
+ }
204
+
205
+ for (const [role, rowId] of [['worker', 'provider_health'], ['reviewer', 'reviewer_health']]) {
206
+ const healthEvidence = currentRouteHealthEvidence({ role, currentSelections, providerHealthChecked, providerHealthBody });
207
+ if (healthEvidence) evidence[rowId] = healthEvidence;
208
+ }
143
209
 
144
210
  const matrix = buildReadinessMatrix({
145
211
  platform,