@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,87 +1,285 @@
1
- // Single, bounded readiness projection shared by the Hub extension surface,
2
- // MCP diagnostics and the client. It consumes already-collected evidence and
3
- // never performs I/O or carries raw task/result/error/credential content.
4
-
5
- const MAX_HEALTH = 128;
6
-
7
- function text(value) { return typeof value === 'string' && value.trim() ? value.trim() : null; }
8
-
9
- function projectRuntime(runtime) {
10
- if (!runtime || typeof runtime !== 'object') return null;
11
- return {
12
- execution_plane: text(runtime.execution_plane),
13
- profile: text(runtime.profile),
14
- listen_port: Number.isFinite(Number(runtime.listen_port)) ? Number(runtime.listen_port) : null,
15
- runtime_id: text(runtime.runtime_id),
16
- };
17
- }
18
-
19
- function projectSelection(value) {
20
- if (!value || typeof value !== 'object') return null;
21
- const provider = text(value.provider);
22
- const model = text(value.model);
23
- if (!provider || !model) return null;
24
- return { provider, model, ...(text(value.source) ? { source: text(value.source) } : {}) };
25
- }
26
-
27
- function projectHealth(entry) {
28
- if (!entry || typeof entry !== 'object') return null;
29
- const provider = text(entry.provider);
30
- const model = text(entry.model);
31
- const state = text(entry.state);
32
- if (!provider || !model || !state) return null;
33
- return {
34
- provider,
35
- model,
36
- state,
37
- ...(text(entry.reason_code) ? { reason_code: text(entry.reason_code) } : {}),
38
- ...(Number.isFinite(entry.observed_at) ? { observed_at: entry.observed_at } : {}),
39
- ...(Number.isFinite(entry.expires_at) ? { expires_at: entry.expires_at } : {}),
40
- fresh: entry.fresh === true,
41
- };
42
- }
43
-
44
- function matrixRow(matrix, id) {
45
- return Array.isArray(matrix?.rows) ? matrix.rows.find((row) => row?.id === id) ?? null : null;
46
- }
47
-
48
- function projectWorkspace(workspace) {
49
- if (!workspace || typeof workspace !== 'object') return null;
50
- return {
51
- ...(text(workspace.status) ? { status: text(workspace.status) } : {}),
52
- ...(text(workspace.reason_code) ? { reason_code: text(workspace.reason_code) } : {}),
53
- };
54
- }
55
-
56
- export function buildRuntimeReadinessSnapshot({
57
- runtime = null,
58
- readinessMatrix = null,
59
- selections = {},
60
- health = [],
61
- jobs = [],
62
- workspace = null,
63
- } = {}) {
64
- const projectedHealth = (Array.isArray(health) ? health : []).map(projectHealth).filter(Boolean).slice(0, MAX_HEALTH);
65
- const projectedJobs = Array.isArray(jobs) ? jobs : [];
66
- const verified = projectedJobs.filter((job) => job?.status === 'done' && job?.task_status === 'success' && job?.delivery_complete === true).length;
67
- const workerJobs = projectedJobs.filter((job) => job?.role === 'worker').length;
68
- const reviewerJobs = projectedJobs.filter((job) => job?.role === 'reviewer').length;
69
- const workerSelection = projectSelection(selections.worker ?? selections.flash);
70
- const reviewerSelection = projectSelection(selections.reviewer ?? selections.pro);
71
- return {
72
- schema_version: 1,
73
- runtime: projectRuntime(runtime),
74
- readiness_matrix: readinessMatrix && typeof readinessMatrix === 'object' ? structuredClone(readinessMatrix) : null,
75
- provider_lifecycle: matrixRow(readinessMatrix, 'provider_lifecycle_consistent'),
76
- health: projectedHealth,
77
- worker: { selected: workerSelection, health: projectedHealth.filter((entry) => entry.provider === workerSelection?.provider && entry.model === workerSelection?.model) },
78
- reviewer: { selected: reviewerSelection, health: projectedHealth.filter((entry) => entry.provider === reviewerSelection?.provider && entry.model === reviewerSelection?.model) },
79
- historical_evidence: {
80
- job_count: projectedJobs.length,
81
- verified_job_count: verified,
82
- worker_job_count: workerJobs,
83
- reviewer_job_count: reviewerJobs,
84
- },
85
- workspace: projectWorkspace(workspace),
86
- };
87
- }
1
+ // Single, bounded readiness projection shared by the Hub extension surface,
2
+ // MCP diagnostics and the client. It consumes already-collected evidence and
3
+ // never performs I/O or carries raw task/result/error/credential content.
4
+
5
+ import { isCompleteRuntimeIdentity, isExactNativeCrewIdentity, sameCompleteRuntimeIdentity } from './runtime-identity-contract.mjs';
6
+ import { MODEL_CALLABILITY_SCHEMA_VERSION, validateModelCallabilityV2 } from './model-callability-contract.mjs';
7
+
8
+ const MAX_HEALTH = 128;
9
+ const DEFAULT_EXECUTION_EVIDENCE_TTL_MS = 5 * 60 * 1000;
10
+ const MIN_EXECUTION_EVIDENCE_TTL_MS = 1_000;
11
+ const MAX_EXECUTION_EVIDENCE_TTL_MS = 24 * 60 * 60 * 1000;
12
+
13
+ function text(value) { return typeof value === 'string' && value.trim() ? value.trim() : null; }
14
+
15
+ function projectRuntime(runtime) {
16
+ if (!runtime || typeof runtime !== 'object') return null;
17
+ return {
18
+ execution_plane: text(runtime.execution_plane),
19
+ profile: text(runtime.profile),
20
+ listen_port: Number.isInteger(runtime.listen_port) ? runtime.listen_port : null,
21
+ runtime_id: text(runtime.runtime_id),
22
+ };
23
+ }
24
+
25
+ function runtimeIdentity(runtime) {
26
+ return isExactNativeCrewIdentity(runtime)
27
+ ? {
28
+ execution_plane: runtime.execution_plane,
29
+ profile: runtime.profile,
30
+ listen_port: runtime.listen_port,
31
+ runtime_id: runtime.runtime_id,
32
+ }
33
+ : null;
34
+ }
35
+
36
+ function projectSelection(value) {
37
+ if (!value || typeof value !== 'object') return null;
38
+ const provider = text(value.provider);
39
+ const model = text(value.model);
40
+ if (!provider || !model) return null;
41
+ return { provider, model, ...(text(value.source) ? { source: text(value.source) } : {}) };
42
+ }
43
+
44
+ function projectHealth(entry) {
45
+ if (!entry || typeof entry !== 'object') return null;
46
+ const provider = text(entry.provider);
47
+ const model = text(entry.model);
48
+ const state = text(entry.state);
49
+ if (!provider || !model || !state) return null;
50
+ return {
51
+ provider,
52
+ model,
53
+ state,
54
+ ...(text(entry.reason_code) ? { reason_code: text(entry.reason_code) } : {}),
55
+ ...(Number.isFinite(entry.observed_at) ? { observed_at: entry.observed_at } : {}),
56
+ ...(Number.isFinite(entry.expires_at) ? { expires_at: entry.expires_at } : {}),
57
+ fresh: entry.fresh === true,
58
+ };
59
+ }
60
+
61
+ function matrixRow(matrix, id) {
62
+ return Array.isArray(matrix?.rows) ? matrix.rows.find((row) => row?.id === id) ?? null : null;
63
+ }
64
+
65
+ function projectWorkspace(workspace) {
66
+ if (!workspace || typeof workspace !== 'object') return null;
67
+ return {
68
+ ...(text(workspace.status) ? { status: text(workspace.status) } : {}),
69
+ ...(text(workspace.reason_code) ? { reason_code: text(workspace.reason_code) } : {}),
70
+ };
71
+ }
72
+
73
+ function routeKey(value) {
74
+ return value?.provider && value?.model ? `${value.provider}\u0000${value.model}` : null;
75
+ }
76
+
77
+ function normalizeExecutionEvidenceTtl(value) {
78
+ const parsed = Number(value);
79
+ if (!Number.isFinite(parsed) || parsed < MIN_EXECUTION_EVIDENCE_TTL_MS) return DEFAULT_EXECUTION_EVIDENCE_TTL_MS;
80
+ return Math.min(Math.floor(parsed), MAX_EXECUTION_EVIDENCE_TTL_MS);
81
+ }
82
+
83
+ function projectModelRole({ role, selected, health, jobs, runtime, now, executionEvidenceTtlMs, enabled }) {
84
+ if (enabled === false) return { state: 'NOT_APPLICABLE', reason_code: 'ROLE_DISABLED', selected: selected ?? null, last_success: null };
85
+ const route = routeKey(selected);
86
+ if (!route || !isExactNativeCrewIdentity(runtime)) return { state: 'UNKNOWN', reason_code: 'MODEL_ROUTE_UNAVAILABLE', selected: selected ?? null, last_success: null };
87
+ const matchingHealth = (Array.isArray(health) ? health : [])
88
+ .filter((entry) => routeKey(entry) === route)
89
+ .sort((left, right) => {
90
+ const observedDelta = (Number(right?.observed_at) || 0) - (Number(left?.observed_at) || 0);
91
+ if (observedDelta !== 0) return observedDelta;
92
+ if (left?.state === 'callable' && right?.state !== 'callable') return 1;
93
+ if (left?.state !== 'callable' && right?.state === 'callable') return -1;
94
+ return 0;
95
+ })[0];
96
+ const expiresAt = Number.isFinite(matchingHealth?.expires_at) ? matchingHealth.expires_at : null;
97
+ const observedAt = Number.isFinite(matchingHealth?.observed_at) ? matchingHealth.observed_at : null;
98
+ if (matchingHealth) {
99
+ if (observedAt === null || observedAt > now || expiresAt === null || expiresAt <= now
100
+ || expiresAt - observedAt > MAX_EXECUTION_EVIDENCE_TTL_MS) {
101
+ return { state: 'STALE', reason_code: 'PROVIDER_HEALTH_STALE_OR_INVALID', selected, observed_at: observedAt, expires_at: expiresAt, source: 'provider_health', last_success: null };
102
+ }
103
+ }
104
+ const healthCurrent = matchingHealth && matchingHealth.fresh === true;
105
+ if (healthCurrent && matchingHealth.state !== 'callable') {
106
+ return { state: 'NOT_CALLABLE', reason_code: matchingHealth.reason_code ?? 'PROVIDER_ROUTE_UNCALLABLE', selected, observed_at: matchingHealth.observed_at ?? null, expires_at: expiresAt, source: 'provider_health', last_success: null };
107
+ }
108
+ if (healthCurrent && matchingHealth.state === 'callable') {
109
+ return { state: 'CALLABLE', reason_code: matchingHealth.reason_code ?? 'PROVIDER_CALLABLE', selected, observed_at: matchingHealth.observed_at ?? null, expires_at: expiresAt, source: 'provider_health', last_success: null };
110
+ }
111
+ const completed = (Array.isArray(jobs) ? jobs : [])
112
+ .filter((job) => job?.role === role && job?.provider === selected.provider && job?.model === selected.model
113
+ && job?.status === 'done' && job?.task_status === 'success'
114
+ && sameCompleteRuntimeIdentity(job.execution_context, runtime))
115
+ .map((job) => ({ job, endedAt: Date.parse(job.endedAt ?? '') }))
116
+ .filter(({ endedAt }) => Number.isFinite(endedAt))
117
+ .sort((a, b) => b.endedAt - a.endedAt)[0];
118
+ if (completed) {
119
+ const expires = completed.endedAt + executionEvidenceTtlMs;
120
+ const lastSuccess = { observed_at: completed.endedAt, expires_at: expires, job_id: completed.job.id };
121
+ if (completed.endedAt > now) return { state: 'STALE', reason_code: 'EXECUTION_EVIDENCE_FUTURE_DATED', selected, observed_at: completed.endedAt, expires_at: expires, source: 'execution', last_success: lastSuccess };
122
+ if (expires > now) return { state: 'CALLABLE', reason_code: 'RECENT_EXECUTION_PASSED', selected, observed_at: completed.endedAt, expires_at: expires, source: 'execution', last_success: lastSuccess };
123
+ return { state: 'STALE', reason_code: 'EXECUTION_EVIDENCE_EXPIRED', selected, observed_at: completed.endedAt, expires_at: expires, source: 'execution', last_success: lastSuccess };
124
+ }
125
+ return { state: 'UNKNOWN', reason_code: 'NO_CURRENT_MODEL_EVIDENCE', selected, last_success: null };
126
+ }
127
+
128
+ export function projectModelCallability({ runtime = null, selections = {}, health = [], health_status = 'AVAILABLE', jobs = [], now = Date.now(), execution_evidence_ttl_ms = DEFAULT_EXECUTION_EVIDENCE_TTL_MS, enabled_roles = {} } = {}) {
129
+ const ttl = normalizeExecutionEvidenceTtl(execution_evidence_ttl_ms);
130
+ const enabledRoles = {
131
+ worker: enabled_roles?.worker !== false,
132
+ reviewer: enabled_roles?.reviewer !== false,
133
+ };
134
+ const roles = {
135
+ worker: projectModelRole({ role: 'worker', selected: selections.worker ?? selections.flash, health, jobs, runtime, now, executionEvidenceTtlMs: ttl, enabled: enabledRoles.worker }),
136
+ reviewer: projectModelRole({ role: 'reviewer', selected: selections.reviewer ?? selections.pro, health, jobs, runtime, now, executionEvidenceTtlMs: ttl, enabled: enabledRoles.reviewer }),
137
+ };
138
+ if (health_status !== 'AVAILABLE') {
139
+ for (const [name, enabled] of Object.entries(enabledRoles)) {
140
+ if (enabled) roles[name] = { ...roles[name], state: 'UNKNOWN', reason_code: 'PROVIDER_HEALTH_UNAVAILABLE', source: 'provider_health' };
141
+ }
142
+ }
143
+ const states = Object.entries(roles).filter(([name]) => enabledRoles[name]).map(([, role]) => role.state);
144
+ const overall = states.length === 0 ? 'UNKNOWN'
145
+ : states.includes('NOT_CALLABLE') ? 'NOT_CALLABLE'
146
+ : states.every((state) => state === 'CALLABLE') ? 'CALLABLE'
147
+ : states.some((state) => state === 'STALE') ? 'STALE' : 'UNKNOWN';
148
+ const expirations = Object.values(roles)
149
+ .map((role) => role?.expires_at)
150
+ .filter((value) => Number.isFinite(value));
151
+ return {
152
+ schema_version: MODEL_CALLABILITY_SCHEMA_VERSION,
153
+ captured_at: now,
154
+ expires_at: expirations.length ? Math.min(...expirations) : null,
155
+ current_runtime_id: runtime?.runtime_id ?? null,
156
+ runtime_identity: runtimeIdentity(runtime),
157
+ execution_evidence_ttl_ms: ttl,
158
+ enabled_roles: enabledRoles,
159
+ overall,
160
+ roles,
161
+ };
162
+ }
163
+
164
+ export function buildRuntimeReadinessSnapshot({
165
+ runtime = null,
166
+ readinessMatrix = null,
167
+ selections = {},
168
+ health = [],
169
+ health_status = null,
170
+ jobs = [],
171
+ workspace = null,
172
+ enabled_roles = {},
173
+ now = Date.now(),
174
+ } = {}) {
175
+ const healthObservations = Array.isArray(health) ? health : (Array.isArray(health?.observations) ? health.observations : []);
176
+ const resolvedHealthStatus = health_status
177
+ ?? (health && !Array.isArray(health) ? health.status : null)
178
+ ?? (Array.isArray(health) && health.length > 0 ? 'AVAILABLE' : 'UNKNOWN');
179
+ const projectedHealthAll = healthObservations.map(projectHealth).filter(Boolean);
180
+ const projectedJobs = Array.isArray(jobs) ? jobs : [];
181
+ const verified = projectedJobs.filter((job) => job?.status === 'done' && job?.task_status === 'success' && job?.delivery_complete === true).length;
182
+ const workerJobs = projectedJobs.filter((job) => job?.role === 'worker').length;
183
+ const reviewerJobs = projectedJobs.filter((job) => job?.role === 'reviewer').length;
184
+ const workerSelection = projectSelection(selections.worker ?? selections.flash);
185
+ const reviewerSelection = projectSelection(selections.reviewer ?? selections.pro);
186
+ const selectedRoutes = [workerSelection, reviewerSelection].filter(Boolean);
187
+ const selectedHealth = selectedRoutes.map((selection) => projectedHealthAll
188
+ .filter((entry) => entry.provider === selection.provider && entry.model === selection.model)
189
+ .sort((left, right) => (right.observed_at ?? 0) - (left.observed_at ?? 0))[0]).filter(Boolean);
190
+ const selectedKeys = new Set(selectedHealth.map((entry) => `${entry.provider}\u0000${entry.model}`));
191
+ const projectedHealth = [...selectedHealth, ...projectedHealthAll
192
+ .filter((entry) => !selectedKeys.has(`${entry.provider}\u0000${entry.model}`))]
193
+ .slice(0, MAX_HEALTH);
194
+ const modelCallability = projectModelCallability({ runtime, selections: { worker: workerSelection, reviewer: reviewerSelection }, health: projectedHealthAll, health_status: resolvedHealthStatus, jobs: projectedJobs, enabled_roles, now });
195
+ return {
196
+ schema_version: 1,
197
+ captured_at: now,
198
+ expires_at: modelCallability.expires_at,
199
+ health_status: resolvedHealthStatus,
200
+ runtime: projectRuntime(runtime),
201
+ runtime_identity: runtimeIdentity(runtime),
202
+ readiness_matrix: readinessMatrix && typeof readinessMatrix === 'object' ? structuredClone(readinessMatrix) : null,
203
+ provider_lifecycle: matrixRow(readinessMatrix, 'provider_lifecycle_consistent'),
204
+ health: projectedHealth,
205
+ worker: { selected: workerSelection, health: projectedHealth.filter((entry) => entry.provider === workerSelection?.provider && entry.model === workerSelection?.model) },
206
+ reviewer: { selected: reviewerSelection, health: projectedHealth.filter((entry) => entry.provider === reviewerSelection?.provider && entry.model === reviewerSelection?.model) },
207
+ historical_evidence: {
208
+ job_count: projectedJobs.length,
209
+ verified_job_count: verified,
210
+ worker_job_count: workerJobs,
211
+ reviewer_job_count: reviewerJobs,
212
+ },
213
+ workspace: projectWorkspace(workspace),
214
+ model_callability: modelCallability,
215
+ };
216
+ }
217
+
218
+ /** Re-project a Hub snapshot when the MCP session changes role enablement. */
219
+ export function reprojectRuntimeModelCallability(snapshot, { enabled_roles = {}, now = Date.now() } = {}) {
220
+ if (!snapshot || typeof snapshot !== 'object') return null;
221
+ const sourceProjection = snapshot.model_callability;
222
+ const sourceValidation = validateModelCallabilityV2({
223
+ projection: sourceProjection,
224
+ runtime: snapshot.runtime,
225
+ expectedEnabledRoles: sourceProjection?.enabled_roles,
226
+ expectedSelections: { worker: snapshot.worker?.selected ?? null, reviewer: snapshot.reviewer?.selected ?? null },
227
+ now,
228
+ });
229
+ if (!sourceValidation.ok) {
230
+ return projectModelCallability({
231
+ runtime: snapshot.runtime,
232
+ selections: { worker: snapshot.worker?.selected, reviewer: snapshot.reviewer?.selected },
233
+ health: [],
234
+ health_status: 'UNAVAILABLE',
235
+ jobs: [],
236
+ enabled_roles,
237
+ now,
238
+ });
239
+ }
240
+ const roles = {};
241
+ for (const role of ['worker', 'reviewer']) {
242
+ if (enabled_roles?.[role] === false) {
243
+ roles[role] = { state: 'NOT_APPLICABLE', reason_code: 'ROLE_DISABLED', selected: null, last_success: null };
244
+ } else if (sourceProjection.enabled_roles?.[role] === true) {
245
+ roles[role] = structuredClone(sourceProjection.roles[role]);
246
+ } else {
247
+ roles[role] = { state: 'UNKNOWN', reason_code: 'ROLE_EVIDENCE_NOT_ENABLED', selected: snapshot[role]?.selected ?? null, last_success: null };
248
+ }
249
+ }
250
+ const activeStates = Object.entries(roles).filter(([role]) => enabled_roles?.[role] === true).map(([, role]) => role.state);
251
+ const overall = activeStates.includes('NOT_CALLABLE') ? 'NOT_CALLABLE'
252
+ : activeStates.length > 0 && activeStates.every((state) => state === 'CALLABLE') ? 'CALLABLE'
253
+ : activeStates.includes('STALE') ? 'STALE' : 'UNKNOWN';
254
+ const expirations = Object.values(roles).map((role) => role?.expires_at).filter((value) => Number.isFinite(value));
255
+ const projected = {
256
+ ...structuredClone(sourceProjection),
257
+ captured_at: now,
258
+ enabled_roles: { worker: enabled_roles.worker === true, reviewer: enabled_roles.reviewer === true },
259
+ roles,
260
+ overall,
261
+ expires_at: expirations.length ? Math.min(...expirations) : null,
262
+ };
263
+ const validation = validateModelCallabilityV2({
264
+ projection: projected,
265
+ runtime: snapshot.runtime,
266
+ expectedEnabledRoles: projected.enabled_roles,
267
+ expectedSelections: {
268
+ worker: enabled_roles.worker === true ? snapshot.worker?.selected ?? null : null,
269
+ reviewer: enabled_roles.reviewer === true ? snapshot.reviewer?.selected ?? null : null,
270
+ },
271
+ now,
272
+ });
273
+ if (!validation.ok) {
274
+ return projectModelCallability({
275
+ runtime: snapshot.runtime,
276
+ selections: { worker: snapshot.worker?.selected, reviewer: snapshot.reviewer?.selected },
277
+ health: [],
278
+ health_status: 'UNAVAILABLE',
279
+ jobs: [],
280
+ enabled_roles,
281
+ now,
282
+ });
283
+ }
284
+ return projected;
285
+ }
package/src/server.mjs CHANGED
@@ -9,8 +9,8 @@ import { hubCompatibilityMessage, resolveHubExecutionMode } from './hub-compatib
9
9
  import { RUNTIME_VERSION, getHubRuntimeIdentity } from './runtime-identity.mjs';
10
10
  import { resolveWorkerModel } from './model-routing.mjs';
11
11
  import { runtimeActivationMetadata } from './runtime-controls.mjs';
12
- import { buildConfigReadinessMatrix } from './config-readiness.mjs';
13
- import { buildRuntimeReadinessSnapshot } from './runtime-readiness-snapshot.mjs';
12
+ import { buildConfigReadinessMatrix } from './config-readiness.mjs';
13
+ import { buildRuntimeReadinessSnapshot, reprojectRuntimeModelCallability } from './runtime-readiness-snapshot.mjs';
14
14
  import { classifyFailure, classifyFailureCode } from './failure-classification.mjs';
15
15
  import {
16
16
  normalizeGlobalConfig,
@@ -57,9 +57,9 @@ const constraintsSchema = z.object({
57
57
  // Initial values come from the global config (~/.config/dsh-crew/config.json,
58
58
  // edited on the DSH settings page); dsh_worker_config overrides per session.
59
59
  // All dispatch decisions (dsh_run_worker AND dsh_spawn_worker, hub AND
60
- // production dispatches go through the 3210-only policy resolver in
61
- // src/policy.mjs; the historical standalone path is retained only for
62
- // read/migration compatibility and is never selected for execution.
60
+ // production dispatches go through the 3210-only policy resolver in
61
+ // src/policy.mjs; the historical standalone path is retained only for
62
+ // read/migration compatibility and is never selected for execution.
63
63
  import { readGlobalConfig } from './install/install.mjs';
64
64
  const initialGlobalConfig = normalizeGlobalConfig(readGlobalConfig());
65
65
  const legacyDefaults = deriveLegacyConfig(initialGlobalConfig);
@@ -68,7 +68,7 @@ const sessionConfig = {
68
68
  enabled: true,
69
69
  default_tier: legacyDefaults.default_tier,
70
70
  default_effort: legacyDefaults.default_effort,
71
- mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode, // auto | hub; legacy standalone is migrated to hub
71
+ mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode, // auto | hub; legacy standalone is migrated to hub
72
72
  default_timeout_seconds: legacyDefaults.default_timeout_seconds,
73
73
  tier_policy: undefined,
74
74
  escalate_on_failure: legacyDefaults.escalate_on_failure,
@@ -86,7 +86,7 @@ function resetSessionConfig() {
86
86
  enabled: true,
87
87
  default_tier: legacyDefaults.default_tier,
88
88
  default_effort: legacyDefaults.default_effort,
89
- mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode,
89
+ mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode,
90
90
  default_timeout_seconds: legacyDefaults.default_timeout_seconds,
91
91
  tier_policy: undefined,
92
92
  escalate_on_failure: legacyDefaults.escalate_on_failure,
@@ -145,9 +145,9 @@ function dispatchDisabled() {
145
145
  });
146
146
  }
147
147
 
148
- async function resolveMode() {
149
- const status = await hubStatus();
150
- const decision = resolveHubExecutionMode(sessionConfig.mode, status, { productionOnly: true });
148
+ async function resolveMode() {
149
+ const status = await hubStatus();
150
+ const decision = resolveHubExecutionMode(sessionConfig.mode, status, { productionOnly: true });
151
151
  if (!decision.ok) {
152
152
  throw Object.assign(new Error(decision.error), {
153
153
  code: decision.code,
@@ -277,7 +277,7 @@ server.registerTool('dsh_worker_config', {
277
277
  enabled: z.boolean().optional().describe('false = refuse all worker dispatch this session'),
278
278
  default_tier: z.enum(['flash', 'pro']).optional(),
279
279
  default_effort: z.enum(['off', 'high', 'max']).optional(),
280
- mode: z.enum(['auto', 'hub']).optional().describe('Execution mode. Production dispatch always uses the isolated 3210 Crew Harness; standalone is retained only as a legacy read/migration value.'),
280
+ mode: z.enum(['auto', 'hub']).optional().describe('Execution mode. Production dispatch uses the official DeepSeek Harness dsh-crew profile on 3210 with the DSH Crew plugin loaded; standalone is retained only as a legacy read/migration value.'),
281
281
  default_timeout_seconds: z.number().int().positive().max(7200).optional(),
282
282
  tier_policy: z.enum(['auto', 'flash-only', 'pro-only']).optional().describe('session hard clamp: flash-only / pro-only pin every dispatch to one tier'),
283
283
  escalate_on_failure: z.boolean().optional().describe('allow an unverified worker attempt to retry through the stronger model policy (applies to run and spawn)'),
@@ -312,6 +312,24 @@ const SAFE_GLOBAL_KEYS = [
312
312
  'preset_flash', 'preset_pro',
313
313
  ];
314
314
 
315
+ function sameHubRuntimeIdentity(status, extension, snapshot) {
316
+ const runtime = extension?.runtime;
317
+ const projected = snapshot?.runtime;
318
+ return status?.compatible === true
319
+ && extension?.schema_version === 1
320
+ && extension?.kind === 'dsh-crew-extension'
321
+ && snapshot?.schema_version === 1
322
+ && runtime?.runtime_id === status.runtime_id
323
+ && runtime?.protocol_version === status.protocol_version
324
+ && runtime?.execution_plane === status.execution_plane
325
+ && runtime?.profile === status.profile
326
+ && runtime?.listen_port === status.listen_port
327
+ && projected?.runtime_id === runtime.runtime_id
328
+ && projected?.execution_plane === runtime.execution_plane
329
+ && projected?.profile === runtime.profile
330
+ && projected?.listen_port === runtime.listen_port;
331
+ }
332
+
315
333
  async function buildConfigReport() {
316
334
  const globalConfig = currentGlobalConfig();
317
335
  const legacy = deriveLegacyConfig(globalConfig);
@@ -324,19 +342,27 @@ async function buildConfigReport() {
324
342
  const mainAgentMode = sessionConfig.main_agent_mode ?? globalConfig.main_agent_mode;
325
343
  const runtimeControls = workflowRuntime.refreshRuntimeControls();
326
344
  const activationBoundaries = globalConfig.config_activation ?? runtimeActivationMetadata();
327
- const hubCompatibility = await hubStatus({ force: true });
345
+ const hubCompatibility = await hubStatus({ force: true, base: globalConfig.hub_url });
328
346
  let effectiveWorkerProvider = null;
329
347
  let effectiveWorkerSelection = { flash: null, pro: null };
330
348
  let providerResolutionError;
331
- let providerCatalogChecked = false;
332
- let providerCatalogBody = null;
333
- let providerInventoryChecked = false;
334
- let providerInventoryBody = null;
335
- let providerHealthChecked = false;
336
- let providerHealthBody = null;
337
- let hubJobsChecked = false;
338
- let hubJobsBody = null;
349
+ let providerCatalogChecked = false;
350
+ let providerCatalogBody = null;
351
+ let hubExtension = null;
352
+ let hubReadinessSnapshot = null;
339
353
  const workerProviderMode = globalConfig.worker_provider_mode ?? 'deepseek-official';
354
+ if (hubCompatibility.compatible) {
355
+ try {
356
+ const extensionRes = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/extension`, { signal: AbortSignal.timeout(800) });
357
+ const extensionBody = await extensionRes.json();
358
+ const candidateExtension = extensionBody?.extension;
359
+ const candidateSnapshot = candidateExtension?.readiness_snapshot;
360
+ if (extensionRes.ok && extensionBody?.ok === true && sameHubRuntimeIdentity(hubCompatibility, candidateExtension, candidateSnapshot)) {
361
+ hubExtension = candidateExtension;
362
+ hubReadinessSnapshot = candidateSnapshot;
363
+ }
364
+ } catch { /* canonical Hub readiness remains unavailable; fallback stays conservative */ }
365
+ }
340
366
  if (workerProviderMode === 'deepseek-official') {
341
367
  effectiveWorkerSelection = {
342
368
  flash: { provider: 'deepseek-official', model: 'deepseek-v4-flash', source: 'legacy-strict' },
@@ -369,61 +395,45 @@ async function buildConfigReport() {
369
395
  } else if (hubCompatibility.reachable) {
370
396
  providerResolutionError = hubCompatibilityMessage(hubCompatibility);
371
397
  }
372
- if (hubCompatibility.compatible) {
373
- try {
374
- providerInventoryChecked = true;
375
- const inventoryRes = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/providers`, { signal: AbortSignal.timeout(800) });
376
- providerInventoryBody = inventoryRes.ok
377
- ? await inventoryRes.json()
378
- : { ok: false, code: 'PROVIDER_INVENTORY_UNAVAILABLE' };
379
- } catch {
380
- providerInventoryChecked = true;
381
- providerInventoryBody = { ok: false, code: 'PROVIDER_INVENTORY_UNAVAILABLE' };
382
- }
383
- }
384
- if (hubCompatibility.compatible) {
385
- try {
386
- providerHealthChecked = true;
387
- const healthRes = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/provider-health`, { signal: AbortSignal.timeout(800) });
388
- providerHealthBody = healthRes.ok ? await healthRes.json() : { ok: false, code: 'PROVIDER_HEALTH_UNAVAILABLE' };
389
- } catch {
390
- providerHealthChecked = true;
391
- providerHealthBody = { ok: false, code: 'PROVIDER_HEALTH_UNAVAILABLE' };
392
- }
393
- }
394
- if (hubCompatibility.compatible) {
395
- try {
396
- hubJobsChecked = true;
397
- const jobsRes = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/jobs`, { signal: AbortSignal.timeout(800) });
398
- hubJobsBody = await jobsRes.json();
399
- } catch {
400
- hubJobsChecked = true;
401
- }
402
- }
403
- effectiveWorkerProvider = effectiveWorkerSelection.flash?.provider ?? null;
404
- const readinessMatrix = buildConfigReadinessMatrix({
398
+ if (hubReadinessSnapshot?.worker?.selected) effectiveWorkerSelection.worker = hubReadinessSnapshot.worker.selected;
399
+ if (hubReadinessSnapshot?.reviewer?.selected) effectiveWorkerSelection.reviewer = hubReadinessSnapshot.reviewer.selected;
400
+ effectiveWorkerProvider = effectiveWorkerSelection.worker?.provider ?? effectiveWorkerSelection.flash?.provider ?? null;
401
+ const fallbackReadinessMatrix = buildConfigReadinessMatrix({
405
402
  hubCompatibility,
406
403
  workerProviderMode,
407
- providerCatalogChecked,
408
- providerCatalogBody,
409
- providerInventoryChecked,
410
- providerInventoryBody,
411
- providerHealthChecked,
412
- providerHealthBody,
413
- hubJobsChecked,
414
- hubJobsBody,
404
+ providerCatalogChecked,
405
+ providerCatalogBody,
415
406
  });
407
+ const readinessMatrix = hubReadinessSnapshot?.readiness_matrix ?? fallbackReadinessMatrix;
416
408
  const roleProfiles = loadRoleProfiles();
417
- const workspaceReadiness = await assessWorkspaceReadiness({ cwd: process.cwd() });
418
- const readinessSnapshot = buildRuntimeReadinessSnapshot({
419
- runtime: getHubRuntimeIdentity(),
420
- readinessMatrix,
421
- selections: { worker: effectiveWorkerSelection.worker ?? effectiveWorkerSelection.flash, reviewer: effectiveWorkerSelection.reviewer ?? effectiveWorkerSelection.pro },
422
- health: providerHealthBody?.health,
423
- jobs: hubJobsBody?.jobs,
424
- workspace: workspaceReadiness,
425
- });
426
- const extensionContract = buildExtensionContract({
409
+ const workspaceReadiness = await assessWorkspaceReadiness({ cwd: process.cwd() });
410
+ const enabledRoles = {
411
+ worker: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false && globalConfig.worker_state !== 'disabled',
412
+ reviewer: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false && globalConfig.review_state !== 'disabled',
413
+ };
414
+ const readinessSnapshot = hubReadinessSnapshot
415
+ ? (() => {
416
+ const snapshot = {
417
+ ...structuredClone(hubReadinessSnapshot),
418
+ readiness_matrix: readinessMatrix,
419
+ workspace: {
420
+ status: workspaceReadiness.status ?? (workspaceReadiness.ok === true ? 'READY' : 'UNAVAILABLE'),
421
+ reason_code: workspaceReadiness.reason_code ?? workspaceReadiness.code ?? 'WORKSPACE_NOT_CHECKED',
422
+ },
423
+ };
424
+ const modelCallability = reprojectRuntimeModelCallability(snapshot, { enabled_roles });
425
+ return modelCallability ? { ...snapshot, model_callability: modelCallability } : snapshot;
426
+ })()
427
+ : buildRuntimeReadinessSnapshot({
428
+ runtime: getHubRuntimeIdentity(),
429
+ readinessMatrix,
430
+ selections: { worker: effectiveWorkerSelection.worker ?? effectiveWorkerSelection.flash, reviewer: effectiveWorkerSelection.reviewer ?? effectiveWorkerSelection.pro },
431
+ health_status: 'UNAVAILABLE',
432
+ workspace: workspaceReadiness,
433
+ enabled_roles: enabledRoles,
434
+ });
435
+ const extensionRuntime = hubExtension?.runtime ?? getHubRuntimeIdentity();
436
+ const extensionContract = buildExtensionContract({
427
437
  config: {
428
438
  ...globalConfig,
429
439
  subagents_enabled: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false,
@@ -431,11 +441,11 @@ async function buildConfigReport() {
431
441
  review_state: globalConfig.review_state,
432
442
  escalate_on_failure: sessionConfig.escalate_on_failure ?? legacy.escalate_on_failure,
433
443
  },
434
- readinessMatrix,
435
- readinessSnapshot,
444
+ readinessMatrix,
445
+ readinessSnapshot,
436
446
  workspace: workspaceReadiness,
437
447
  profiles: roleProfiles,
438
- runtime: getHubRuntimeIdentity(),
448
+ runtime: extensionRuntime,
439
449
  });
440
450
  return {
441
451
  enabled: sessionConfig.enabled,
@@ -474,8 +484,8 @@ async function buildConfigReport() {
474
484
  global_defaults: Object.fromEntries(SAFE_GLOBAL_KEYS.map((k) => [k, globalConfig[k]])),
475
485
  runtime_controls: runtimeControls,
476
486
  activation_boundaries: activationBoundaries,
477
- readiness_matrix: readinessMatrix,
478
- readiness_snapshot: readinessSnapshot,
487
+ readiness_matrix: readinessMatrix,
488
+ readiness_snapshot: readinessSnapshot,
479
489
  role_profiles: roleProfiles,
480
490
  extension_contract: extensionContract,
481
491
  hub_reachable: hubCompatibility.reachable,
@@ -0,0 +1,23 @@
1
+ /** Keep Hub-only MCP clients independent of optional standalone host peers. */
2
+ export function createStandaloneHarness(options, { load = () => import('@deepseek-ai/dsh-sdk-client') } = {}) {
3
+ let loading;
4
+ let harness;
5
+ let closed = false;
6
+ return {
7
+ async run(...args) {
8
+ if (closed) throw Error('standalone job cancelled before startup');
9
+ loading ??= load().catch(error => {
10
+ if (error?.code !== 'ERR_MODULE_NOT_FOUND' && error?.code !== 'MODULE_NOT_FOUND') throw error;
11
+ throw Object.assign(new Error('Standalone DSH SDK is unavailable. Repair the Crew-owned installation before using standalone mode; Hub mode does not require this SDK.'), { code: 'STANDALONE_SDK_UNAVAILABLE' });
12
+ });
13
+ const { DeepSeekHarness } = await loading;
14
+ if (closed) throw Error('standalone job cancelled before startup');
15
+ harness ??= new DeepSeekHarness(options);
16
+ return harness.run(...args);
17
+ },
18
+ async close() {
19
+ closed = true;
20
+ if (harness) await harness.close();
21
+ },
22
+ };
23
+ }