@ran-sh/dsh-crew 1.9.0 → 1.10.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.
Files changed (151) hide show
  1. package/.claude-plugin/marketplace.json +16 -16
  2. package/.claude-plugin/plugin.json +14 -14
  3. package/LICENSE +21 -21
  4. package/README.de.md +359 -359
  5. package/README.es.md +359 -359
  6. package/README.fr.md +359 -359
  7. package/README.hi.md +359 -359
  8. package/README.id.md +359 -359
  9. package/README.ja.md +359 -359
  10. package/README.ko.md +359 -359
  11. package/README.md +126 -126
  12. package/README.pt.md +359 -359
  13. package/README.ru.md +359 -359
  14. package/README.th.md +359 -359
  15. package/README.tr.md +359 -359
  16. package/README.vi.md +359 -359
  17. package/README.zh-TW.md +359 -359
  18. package/README.zh.md +116 -116
  19. package/agents/ds-flash.md +24 -24
  20. package/agents/ds-pro.md +24 -24
  21. package/agents/ds-reviewer.md +23 -23
  22. package/agents/ds-worker.md +23 -23
  23. package/bin/dsh-crew.mjs +16 -16
  24. package/codex/agents/ds-flash.toml +34 -34
  25. package/codex/agents/ds-pro.toml +34 -34
  26. package/codex/agents/ds-reviewer.toml +32 -32
  27. package/codex/agents/ds-worker.toml +32 -32
  28. package/codex/prompts/dsh-config.md +19 -19
  29. package/codex/prompts/dsh-status.md +5 -5
  30. package/commands/config.md +23 -23
  31. package/commands/off.md +5 -5
  32. package/commands/on.md +5 -5
  33. package/commands/status.md +9 -9
  34. package/cordis.patch.yml +4 -4
  35. package/docs/gpt-relay-extension.md +103 -103
  36. package/docs/installation.md +138 -138
  37. package/docs/job-contracts.md +103 -103
  38. package/docs/readiness-matrix.md +85 -85
  39. package/docs/ui-surfaces.md +107 -107
  40. package/official-web-bridge/cordis.patch.yml +4 -4
  41. package/official-web-bridge/entry.mjs +1 -1
  42. package/official-web-bridge/overlay-entry.mjs +59 -59
  43. package/official-web-bridge/package.json +25 -25
  44. package/package.json +3 -2
  45. package/scripts/build-client.mjs +49 -49
  46. package/scripts/live-crew-smoke.mjs +39 -39
  47. package/scripts/live-policy-matrix.mjs +177 -177
  48. package/scripts/policy-probe.mjs +101 -101
  49. package/scripts/remove-legacy-official-bridge.ps1 +89 -89
  50. package/scripts/setup.mjs +393 -393
  51. package/scripts/smoke-real.mjs +110 -110
  52. package/scripts/smoke.mjs +78 -78
  53. package/scripts/verify-crew-ui-polish.mjs +145 -145
  54. package/scripts/verify-history-ui.mjs +97 -97
  55. package/scripts/verify-installer-fix.mjs +26 -26
  56. package/scripts/verify-npm-install.mjs +311 -311
  57. package/scripts/verify-official-bridge-e2e.mjs +192 -192
  58. package/src/adaptive-routing.mjs +260 -260
  59. package/src/client/activation-summary.tsx +64 -64
  60. package/src/client/collapsible-sections.mjs +55 -55
  61. package/src/client/history-panel.tsx +108 -108
  62. package/src/client/host-readiness.mjs +71 -71
  63. package/src/client/index.tsx +1711 -1711
  64. package/src/client/model-callability-view.mjs +17 -17
  65. package/src/client/panel-chrome.tsx +40 -40
  66. package/src/client/quick-entry.tsx +10 -10
  67. package/src/client/quick-panel.tsx +275 -275
  68. package/src/client/readiness-envelope.mjs +34 -34
  69. package/src/client/surface-detection.mjs +43 -43
  70. package/src/config-readiness.mjs +226 -226
  71. package/src/credential-reference.mjs +38 -38
  72. package/src/delivery.mjs +205 -205
  73. package/src/dsh-cli-runtime.mjs +1021 -1021
  74. package/src/dsh-cohort.mjs +20 -20
  75. package/src/extension-contract.mjs +104 -104
  76. package/src/failure-classification.mjs +201 -201
  77. package/src/history/admission-gate.mjs +67 -67
  78. package/src/history/archive-store.mjs +272 -272
  79. package/src/history/cleanup-plan.mjs +89 -89
  80. package/src/history/http.mjs +32 -32
  81. package/src/history/operation.mjs +86 -86
  82. package/src/history/runner-detach.mjs +34 -34
  83. package/src/history/runner.mjs +52 -52
  84. package/src/history/runtime.mjs +36 -36
  85. package/src/history/service.mjs +177 -177
  86. package/src/history/state.mjs +27 -27
  87. package/src/hub/entry.mjs +104 -104
  88. package/src/hub/index.mjs +2694 -2694
  89. package/src/hub-client.mjs +154 -154
  90. package/src/hub-compatibility.mjs +42 -42
  91. package/src/i18n.mjs +19 -19
  92. package/src/information-flow.mjs +67 -67
  93. package/src/install/cli.mjs +28 -28
  94. package/src/install/install-legacy.mjs +711 -711
  95. package/src/install/install.mjs +483 -483
  96. package/src/install/npx-lifecycle.mjs +3456 -3456
  97. package/src/install/official-frontend-assets.mjs +78 -78
  98. package/src/install/official-web.mjs +95 -95
  99. package/src/install/payload-content.mjs +88 -88
  100. package/src/install/windows-startup.mjs +236 -236
  101. package/src/install/windows-supervisor-adapter.mjs +443 -443
  102. package/src/install/windows-supervisor-lifecycle.mjs +782 -782
  103. package/src/install/zcode.mjs +397 -397
  104. package/src/job-contracts.mjs +255 -255
  105. package/src/local-request-guard.mjs +60 -60
  106. package/src/mcp-runtime.mjs +340 -340
  107. package/src/model-callability-contract.mjs +79 -79
  108. package/src/model-catalog.mjs +180 -180
  109. package/src/model-routing.mjs +586 -586
  110. package/src/model-schedule.mjs +207 -207
  111. package/src/official-web-bridge.mjs +447 -447
  112. package/src/policy.mjs +235 -235
  113. package/src/provider-delete-adapters.mjs +1934 -1934
  114. package/src/provider-health.mjs +130 -130
  115. package/src/provider-inventory.mjs +182 -182
  116. package/src/provider-layer-migration-adapters.mjs +759 -759
  117. package/src/provider-layer-migration.mjs +198 -198
  118. package/src/provider-lifecycle-state.mjs +103 -103
  119. package/src/provider-lifecycle.mjs +252 -252
  120. package/src/provider-profile-store.mjs +390 -390
  121. package/src/provider-settings-store.mjs +633 -633
  122. package/src/provider-store-lock.mjs +67 -67
  123. package/src/readiness-matrix.mjs +181 -181
  124. package/src/removable-waiter.mjs +29 -29
  125. package/src/role-profiles.mjs +107 -107
  126. package/src/runtime-controls.mjs +84 -84
  127. package/src/runtime-identity-contract.mjs +34 -34
  128. package/src/runtime-identity.mjs +235 -235
  129. package/src/runtime-readiness-snapshot.mjs +285 -285
  130. package/src/server.mjs +581 -581
  131. package/src/session-origins.mjs +60 -60
  132. package/src/standalone-sdk.mjs +23 -23
  133. package/src/status-shard.mjs +63 -63
  134. package/src/structured-error-code.mjs +38 -38
  135. package/src/supervisor/restart-request.mjs +256 -256
  136. package/src/workflow-runtime.mjs +739 -739
  137. package/src/workflow.mjs +155 -155
  138. package/src/workspace-audit.mjs +231 -231
  139. package/src/workspace-context.mjs +146 -146
  140. package/src/workspace-isolation.mjs +463 -455
  141. package/src/workspace-readiness.mjs +32 -32
  142. package/statusline/statusline.sh +14 -14
  143. package/statusline/worker-segment.sh +35 -35
  144. package/windows/start-dsh-crew.cmd +57 -57
  145. package/windows/start-dsh-crew.ps1 +1302 -1302
  146. package/windows/supervisor-control.ps1 +467 -467
  147. package/worker.cordis.yml +67 -67
  148. package/zcode/agents/ds-reviewer.md +31 -31
  149. package/zcode/agents/ds-worker.md +31 -31
  150. package/zcode/commands/dsh-config.md +17 -17
  151. package/zcode/commands/dsh-status.md +5 -5
package/src/server.mjs CHANGED
@@ -1,581 +1,581 @@
1
- // MCP stdio server exposing the DSH worker pool to Claude Code / Codex.
2
-
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
- import { z } from 'zod';
6
- import { startJob, waitJob, cancelJob, listJobs, getJob, jobView } from './jobs.mjs';
7
- import { hubStatus, hub } from './hub-client.mjs';
8
- import { hubCompatibilityMessage, resolveHubExecutionMode } from './hub-compatibility.mjs';
9
- import { RUNTIME_VERSION, getHubRuntimeIdentity } from './runtime-identity.mjs';
10
- import { resolveWorkerModel } from './model-routing.mjs';
11
- import { runtimeActivationMetadata } from './runtime-controls.mjs';
12
- import { buildConfigReadinessMatrix } from './config-readiness.mjs';
13
- import { buildRuntimeReadinessSnapshot, reprojectRuntimeModelCallability } from './runtime-readiness-snapshot.mjs';
14
- import { classifyFailure, classifyFailureCode } from './failure-classification.mjs';
15
- import {
16
- normalizeGlobalConfig,
17
- deriveLegacyConfig,
18
- getEffectiveTierState,
19
- getRoutingGuidance,
20
- chooseDefaultTier,
21
- canDispatchRole,
22
- resolveRoleTierHint,
23
- shouldAutoReview,
24
- } from './policy.mjs';
25
- import { buildMcpWorkflowRuntime } from './mcp-runtime.mjs';
26
- import { buildReviewTask } from './information-flow.mjs';
27
- import { projectWorkflowView } from './job-contracts.mjs';
28
- import { loadRoleProfiles, resolveRoleProfile } from './role-profiles.mjs';
29
- import { loadWorkspaceContexts, resolveWorkspaceContext, buildWorkspaceTask, addContextReferences, isSafeBranchName } from './workspace-context.mjs';
30
- import { buildExtensionContract } from './extension-contract.mjs';
31
- import { assessWorkspaceReadiness } from './workspace-readiness.mjs';
32
- import { detectOrchestrator } from './orchestrator.mjs';
33
-
34
- const server = new McpServer({ name: 'dsh-crew', version: RUNTIME_VERSION });
35
-
36
- const tierSchema = z.enum(['flash', 'pro']).optional().describe('Legacy worker tier (compatibility only): Flash/Pro now act as a model-class hint, not a role. Prefer role=worker / role=reviewer; the backend resolves the actual provider/model from the Model Policy.');
37
- const roleSchema = z.enum(['worker', 'reviewer']).optional().describe('Dispatch role: worker executes implementation / fixes / tests / search; reviewer independently reviews a completed implementation. A coding request defaults to worker; reviewer runs on explicit request or via the automatic review workflow.');
38
- const effortSchema = z.enum(['off', 'high', 'max']).optional().describe('Reasoning effort for the worker. Omit to use the session default.');
39
- const detailSchema = z.enum(['compact', 'full']).default('compact').describe('compact returns the bounded Result Contract; full explicitly includes raw workflow/candidate details for debugging.');
40
- const profileSchema = z.string().max(64).optional().describe('Versioned Worker/Reviewer profile id; defaults by role.');
41
- const workspaceIdSchema = z.string().max(64).optional().describe('Workspace Context id from Crew-owned workspaces.json.');
42
- const contextRefsSchema = z.array(z.string().max(256)).max(32).optional().describe('Additional workspace-relative instruction references; contents are not copied.');
43
- const clientJobIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/).optional().describe('Optional caller id echoed in events and evidence; Crew still assigns its own workflow id.');
44
- const workspaceSchema = z.object({
45
- repo_root: z.string().optional(),
46
- branch: z.string().refine(isSafeBranchName, 'invalid git branch name').optional(),
47
- worktree: z.enum(['auto', 'existing', 'none']).optional(),
48
- }).optional().describe('Per-job workspace overrides. auto isolates coding Workers; existing/none use the supplied workspace.');
49
- const constraintsSchema = z.object({
50
- timeout_seconds: z.number().int().positive().max(7200).optional(),
51
- allow_fallback: z.boolean().optional(),
52
- allow_no_changes: z.boolean().optional().describe('Explicitly allow a verified read-only or analysis job to succeed with zero workspace changes.'),
53
- }).optional().describe('Per-job constraints override profile and session defaults.');
54
-
55
- // Session-level configuration. This MCP server process lives exactly as long
56
- // as one Claude Code / Codex session, so plain memory IS session scope.
57
- // Initial values come from the global config (~/.config/dsh-crew/config.json,
58
- // edited on the DSH settings page); dsh_worker_config overrides per session.
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.
63
- import { readGlobalConfig } from './install/install.mjs';
64
- const initialGlobalConfig = normalizeGlobalConfig(readGlobalConfig());
65
- const legacyDefaults = deriveLegacyConfig(initialGlobalConfig);
66
- const currentGlobalConfig = () => normalizeGlobalConfig(readGlobalConfig());
67
- const sessionConfig = {
68
- enabled: true,
69
- default_tier: legacyDefaults.default_tier,
70
- default_effort: legacyDefaults.default_effort,
71
- mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode, // auto | hub; legacy standalone is migrated to hub
72
- default_timeout_seconds: legacyDefaults.default_timeout_seconds,
73
- tier_policy: undefined,
74
- escalate_on_failure: legacyDefaults.escalate_on_failure,
75
- preset_flash: legacyDefaults.preset_flash ?? 'default',
76
- preset_pro: legacyDefaults.preset_pro ?? 'default',
77
- collaboration_mode: undefined,
78
- main_agent_mode: undefined,
79
- flash_state: undefined,
80
- pro_state: undefined,
81
- pro_reviews_flash: undefined,
82
- };
83
-
84
- function resetSessionConfig() {
85
- Object.assign(sessionConfig, {
86
- enabled: true,
87
- default_tier: legacyDefaults.default_tier,
88
- default_effort: legacyDefaults.default_effort,
89
- mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode,
90
- default_timeout_seconds: legacyDefaults.default_timeout_seconds,
91
- tier_policy: undefined,
92
- escalate_on_failure: legacyDefaults.escalate_on_failure,
93
- preset_flash: legacyDefaults.preset_flash ?? 'default',
94
- preset_pro: legacyDefaults.preset_pro ?? 'default',
95
- collaboration_mode: undefined,
96
- main_agent_mode: undefined,
97
- flash_state: undefined,
98
- pro_state: undefined,
99
- pro_reviews_flash: undefined,
100
- });
101
- }
102
-
103
- function presetForTier(tier) {
104
- const p = tier === 'flash' ? sessionConfig.preset_flash : sessionConfig.preset_pro;
105
- return !p || p === 'default' ? undefined : p;
106
- }
107
-
108
- function text(obj) {
109
- let payload = obj;
110
- if (obj && typeof obj === 'object' && !Array.isArray(obj) && obj.failure === undefined) {
111
- if (obj.code) {
112
- payload = { ...obj, failure: classifyFailureCode(obj.code) };
113
- } else if (obj.status !== undefined || obj.phase !== undefined || obj.outcome !== undefined || obj.error_code !== undefined) {
114
- payload = {
115
- ...obj,
116
- failure: classifyFailure({
117
- phase: obj.phase,
118
- status: obj.status,
119
- errorCode: obj.error_code,
120
- outcome: obj.outcome,
121
- decision: obj.decision,
122
- review: obj.review,
123
- childAttempts: obj.child_attempts,
124
- }),
125
- };
126
- }
127
- }
128
- return { content: [{ type: 'text', text: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2) }] };
129
- }
130
-
131
- const ORCHESTRATOR = detectOrchestrator();
132
-
133
- function policyRejection(decision) {
134
- return text({
135
- error: decision.error.message,
136
- code: decision.error.policyCode,
137
- note: 'DSH Crew policy rejection — report this to the user instead of doing the task yourself.',
138
- });
139
- }
140
-
141
- function dispatchDisabled() {
142
- return text({
143
- error: 'worker dispatch is disabled for this session (set via dsh_worker_config). Report this to the user instead of doing the task yourself.',
144
- code: 'SUBAGENTS_DISABLED',
145
- });
146
- }
147
-
148
- async function resolveMode() {
149
- const status = await hubStatus();
150
- const decision = resolveHubExecutionMode(sessionConfig.mode, status, { productionOnly: true });
151
- if (!decision.ok) {
152
- throw Object.assign(new Error(decision.error), {
153
- code: decision.code,
154
- hubStatus: status,
155
- });
156
- }
157
- return decision.mode;
158
- }
159
-
160
- const workflowRuntime = buildMcpWorkflowRuntime({
161
- getSessionConfig: () => sessionConfig,
162
- resolveMode,
163
- presetForTier,
164
- readGlobalConfig,
165
- buildReviewTask,
166
- attemptTimeoutMs: () => (sessionConfig.default_timeout_seconds ?? 1800) * 1000,
167
- });
168
-
169
- function dispatchError(code, details = {}) {
170
- return { ok: false, response: text({ error: details.error ?? code, code, ...details }) };
171
- }
172
-
173
- function prepareDispatch({ task, role, tier, legacy_tier, effort, cwd, timeout_seconds, profile, workspace_id, context_refs, job_id, workspace: workspaceOverride, constraints }) {
174
- const globalConfig = currentGlobalConfig();
175
- const profileRegistry = loadRoleProfiles();
176
- if (!profileRegistry.ok) return dispatchError('PROFILE_FILE_INVALID', { errors: profileRegistry.errors });
177
- const profileRole = profile ? profileRegistry.profiles?.[profile]?.role : undefined;
178
- const requestedRole = role ?? profileRole;
179
- const hint = resolveRoleTierHint(requestedRole, legacy_tier ?? tier);
180
- if (!hint.ok) return dispatchError(hint.code, { error: hint.error });
181
- const effRole = hint.role;
182
- const resolvedProfile = resolveRoleProfile(profileRegistry, profile, effRole);
183
- if (!resolvedProfile.ok) return dispatchError(resolvedProfile.code, resolvedProfile);
184
-
185
- let effTier;
186
- let decision;
187
- if (requestedRole === undefined) {
188
- decision = chooseDefaultTier(globalConfig, legacy_tier ?? tier, sessionConfig);
189
- if (!decision.ok) return { ok: false, response: policyRejection(decision) };
190
- effTier = decision.tier;
191
- } else {
192
- decision = canDispatchRole(globalConfig, effRole, true, sessionConfig);
193
- if (!decision.ok) return { ok: false, response: policyRejection(decision) };
194
- if (effRole === 'reviewer') effTier = 'pro';
195
- else if (legacy_tier !== undefined) effTier = legacy_tier;
196
- else if (tier !== undefined) effTier = tier;
197
- else { const slot = chooseDefaultTier(globalConfig, undefined, sessionConfig); effTier = slot.ok ? slot.tier : 'flash'; }
198
- }
199
-
200
- const workDir = workspaceOverride?.repo_root ?? cwd ?? process.cwd();
201
- const workspace = resolveWorkspaceContext(loadWorkspaceContexts(), { workspace_id, cwd: workDir });
202
- if (!workspace.ok) return dispatchError(workspace.code, workspace);
203
- const withRefs = addContextReferences(workspace.context, context_refs, { cwd: workDir });
204
- if (!withRefs.ok) return dispatchError(withRefs.code);
205
- const profileValue = resolvedProfile.profile;
206
- const effectiveTimeout = constraints?.timeout_seconds ?? timeout_seconds ?? profileValue.timeout_seconds ?? sessionConfig.default_timeout_seconds;
207
- const requestedIsolation = workspaceOverride?.worktree === 'auto'
208
- ? 'worktree'
209
- : workspaceOverride?.worktree === 'existing' || workspaceOverride?.worktree === 'none'
210
- ? 'shared'
211
- : profileValue.isolation;
212
- return {
213
- ok: true,
214
- role: effRole,
215
- timeout: effectiveTimeout,
216
- spec: {
217
- client_job_id: job_id ?? null,
218
- role: effRole,
219
- delivery: effRole === 'reviewer' ? 'review' : 'coding',
220
- model_class_hint: effTier,
221
- task: buildWorkspaceTask(task, withRefs.context),
222
- cwd: workDir,
223
- effort: effort ?? sessionConfig.default_effort,
224
- source: ORCHESTRATOR,
225
- profile_id: resolvedProfile.profile_id,
226
- requested_isolation: requestedIsolation,
227
- workspace_branch: workspaceOverride?.branch ?? withRefs.context?.default_branch ?? null,
228
- timeout_seconds: effectiveTimeout,
229
- allow_fallback: constraints?.allow_fallback ?? profileValue.fallback,
230
- allow_no_changes: constraints?.allow_no_changes === true,
231
- routing: profileValue.routing,
232
- review_strictness: profileValue.review_strictness,
233
- workspace_context: withRefs.context,
234
- },
235
- };
236
- }
237
-
238
- server.registerTool('dsh_run_worker', {
239
- title: 'Run DSH worker (blocking)',
240
- description: 'Delegate a task to a DSH (DeepSeek Harness) coding agent and wait for its final result. The worker is a full DSH agent with its own tools and sandbox. Pass role=worker for implementation and role=reviewer for an independent review pass. Disabled roles are refused by the DSH Crew policy; Manual roles only run when explicitly requested. Blocks until the worker finishes.',
241
- inputSchema: {
242
- task: z.string().describe('Full task description for the worker, self-contained'),
243
- role: roleSchema,
244
- tier: tierSchema,
245
- legacy_tier: tierSchema.describe('Legacy model-class hint (flash | pro), forwarded verbatim by deprecated ds-flash/ds-pro aliases; only influences which model class backs a worker role, never the role gate'),
246
- effort: effortSchema,
247
- cwd: z.string().optional().describe('Workspace directory for the worker (defaults to current project)'),
248
- timeout_seconds: z.number().int().positive().max(7200).optional(),
249
- job_id: clientJobIdSchema,
250
- workspace: workspaceSchema,
251
- constraints: constraintsSchema,
252
- profile: profileSchema,
253
- workspace_id: workspaceIdSchema,
254
- context_refs: contextRefsSchema,
255
- detail: detailSchema,
256
- },
257
- }, async ({ task, role, tier, legacy_tier, effort, cwd, timeout_seconds, profile, workspace_id, context_refs, job_id, workspace, constraints, detail }) => {
258
- if (!sessionConfig.enabled) return dispatchDisabled();
259
- const prepared = prepareDispatch({ task, role, tier, legacy_tier, effort, cwd, timeout_seconds, profile, workspace_id, context_refs, job_id, workspace, constraints });
260
- if (!prepared.ok) return prepared.response;
261
- const wf = workflowRuntime.start(prepared.spec);
262
- await workflowRuntime.wait(wf.id, prepared.timeout * 1000);
263
- const view = workflowRuntime.get(wf.id, { withResult: true });
264
- if (view.status === 'running') {
265
- return text({ ...projectWorkflowView(view, { detail }), note: `still running after ${prepared.timeout}s; poll with dsh_worker_result`, status: 'running' });
266
- }
267
- if (view.phase === 'failed') {
268
- return text({ ...projectWorkflowView(view, { detail }), note: 'workflow failed — see error / error_code.', status: 'failed' });
269
- }
270
- return text({ ...projectWorkflowView(view, { detail }), note: prepared.role === 'reviewer' ? 'review complete' : 'workflow complete' });
271
- });
272
-
273
- server.registerTool('dsh_worker_config', {
274
- title: 'Session worker configuration',
275
- description: 'Read or update session-level worker settings: enable/disable dispatch, default tier/effort/timeout, execution mode, tier policy, escalation, collaboration mode, per-tier state and review behavior. Call with no arguments to read the current configuration, runtime control state, activation boundaries, global defaults, session overrides, effective policy and routing guidance. Settings last for this session only.',
276
- inputSchema: {
277
- enabled: z.boolean().optional().describe('false = refuse all worker dispatch this session'),
278
- default_tier: z.enum(['flash', 'pro']).optional(),
279
- default_effort: z.enum(['off', 'high', 'max']).optional(),
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
- default_timeout_seconds: z.number().int().positive().max(7200).optional(),
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
- escalate_on_failure: z.boolean().optional().describe('allow an unverified worker attempt to retry through the stronger model policy (applies to run and spawn)'),
284
- preset_flash: z.string().optional().describe('hub-mode agent preset for flash workers (preset id, or "default")'),
285
- preset_pro: z.string().optional().describe('hub-mode agent preset for pro workers (preset id, or "default")'),
286
- collaboration_mode: z.enum(['flash-only', 'pro-only', 'balanced', 'review-pipeline', 'custom']).optional().describe('session override of the global collaboration mode'),
287
- main_agent_mode: z.enum(['direct-allowed', 'coordinator-first', 'dispatcher-only']).optional().describe('session override of the host routing guidance'),
288
- flash_state: z.enum(['disabled', 'manual', 'auto']).optional().describe('session override of the flash tier state (custom mode)'),
289
- pro_state: z.enum(['disabled', 'manual', 'auto']).optional().describe('session override of the pro tier state (custom mode)'),
290
- pro_reviews_flash: z.boolean().optional().describe('enable the automatic reviewer after a verified worker workflow (applies to run and spawn)'),
291
- reset: z.boolean().optional().describe('true = restore all defaults first'),
292
- },
293
- }, async ({ reset, ...patch }) => {
294
- if (reset) resetSessionConfig();
295
- if (patch.tier_policy === 'auto') {
296
- sessionConfig.tier_policy = undefined;
297
- sessionConfig.collaboration_mode = 'balanced';
298
- delete patch.tier_policy;
299
- }
300
- for (const [k, v] of Object.entries(patch)) if (v !== undefined) sessionConfig[k] = v;
301
- return text(await buildConfigReport());
302
- });
303
-
304
- const SAFE_GLOBAL_KEYS = [
305
- 'default_tier', 'default_effort', 'mode', 'default_timeout_seconds', 'hub_url',
306
- 'tier_policy', 'escalate_on_failure', 'subagents_enabled', 'collaboration_mode',
307
- 'main_agent_mode', 'flash_state', 'pro_state', 'flash_roles', 'pro_roles',
308
- 'pro_reviews_flash', 'worker_provider_mode',
309
- 'flash_model_priority', 'flash_model_priority_configured', 'flash_model_fallback',
310
- 'pro_model_priority', 'pro_model_priority_configured', 'pro_model_fallback',
311
- 'preset_flash', 'preset_pro',
312
- ];
313
-
314
- function sameHubRuntimeIdentity(status, extension, snapshot) {
315
- const runtime = extension?.runtime;
316
- const projected = snapshot?.runtime;
317
- return status?.compatible === true
318
- && extension?.schema_version === 1
319
- && extension?.kind === 'dsh-crew-extension'
320
- && snapshot?.schema_version === 1
321
- && runtime?.runtime_id === status.runtime_id
322
- && runtime?.protocol_version === status.protocol_version
323
- && runtime?.execution_plane === status.execution_plane
324
- && runtime?.profile === status.profile
325
- && runtime?.listen_port === status.listen_port
326
- && projected?.runtime_id === runtime.runtime_id
327
- && projected?.execution_plane === runtime.execution_plane
328
- && projected?.profile === runtime.profile
329
- && projected?.listen_port === runtime.listen_port;
330
- }
331
-
332
- async function buildConfigReport() {
333
- const globalConfig = currentGlobalConfig();
334
- const legacy = deriveLegacyConfig(globalConfig);
335
- const flashState = getEffectiveTierState(globalConfig, 'flash', sessionConfig);
336
- const proState = getEffectiveTierState(globalConfig, 'pro', sessionConfig);
337
- const defaultDecision = chooseDefaultTier(globalConfig, undefined, sessionConfig);
338
- const overrides = {};
339
- for (const [k, v] of Object.entries(sessionConfig)) if (v !== undefined) overrides[k] = v;
340
- const collaborationMode = sessionConfig.collaboration_mode ?? globalConfig.collaboration_mode;
341
- const mainAgentMode = sessionConfig.main_agent_mode ?? globalConfig.main_agent_mode;
342
- const runtimeControls = workflowRuntime.refreshRuntimeControls();
343
- const activationBoundaries = globalConfig.config_activation ?? runtimeActivationMetadata();
344
- const hubCompatibility = await hubStatus({ force: true, base: globalConfig.hub_url });
345
- let effectiveWorkerProvider = null;
346
- let effectiveWorkerSelection = { flash: null, pro: null };
347
- let providerResolutionError;
348
- let providerCatalogChecked = false;
349
- let providerCatalogBody = null;
350
- let hubExtension = null;
351
- let hubReadinessSnapshot = null;
352
- const workerProviderMode = globalConfig.worker_provider_mode ?? 'deepseek-official';
353
- if (hubCompatibility.compatible) {
354
- try {
355
- const extensionRes = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/extension`, { signal: AbortSignal.timeout(800) });
356
- const extensionBody = await extensionRes.json();
357
- const candidateExtension = extensionBody?.extension;
358
- const candidateSnapshot = candidateExtension?.readiness_snapshot;
359
- if (extensionRes.ok && extensionBody?.ok === true && sameHubRuntimeIdentity(hubCompatibility, candidateExtension, candidateSnapshot)) {
360
- hubExtension = candidateExtension;
361
- hubReadinessSnapshot = candidateSnapshot;
362
- }
363
- } catch { /* canonical Hub readiness remains unavailable; fallback stays conservative */ }
364
- }
365
- if (workerProviderMode === 'deepseek-official') {
366
- effectiveWorkerSelection = {
367
- flash: { provider: 'deepseek-official', model: 'deepseek-v4-flash', source: 'legacy-strict' },
368
- pro: { provider: 'deepseek-official', model: 'deepseek-v4-pro', source: 'legacy-strict' },
369
- };
370
- } else if (hubCompatibility.compatible) {
371
- try {
372
- providerCatalogChecked = true;
373
- const res = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/models`, { signal: AbortSignal.timeout(800) });
374
- const body = await res.json();
375
- providerCatalogBody = body;
376
- if (!body?.ok) providerResolutionError = body?.error ?? 'Unable to read Harness model catalog.';
377
- else for (const tier of ['flash', 'pro']) {
378
- const selected = resolveWorkerModel({
379
- tier,
380
- priority: globalConfig[`${tier}_model_priority`],
381
- priorityConfigured: globalConfig[`${tier}_model_priority_configured`],
382
- fallback: globalConfig[`${tier}_model_fallback`],
383
- catalog: body,
384
- harnessDefault: body.harness_default,
385
- });
386
- effectiveWorkerSelection[tier] = selected.ok
387
- ? { provider: selected.provider, model: selected.model, source: selected.source }
388
- : { code: selected.code, error: selected.message };
389
- }
390
- } catch (err) {
391
- providerCatalogChecked = true;
392
- providerResolutionError = err?.message ?? String(err);
393
- }
394
- } else if (hubCompatibility.reachable) {
395
- providerResolutionError = hubCompatibilityMessage(hubCompatibility);
396
- }
397
- if (hubReadinessSnapshot?.worker?.selected) effectiveWorkerSelection.worker = hubReadinessSnapshot.worker.selected;
398
- if (hubReadinessSnapshot?.reviewer?.selected) effectiveWorkerSelection.reviewer = hubReadinessSnapshot.reviewer.selected;
399
- effectiveWorkerProvider = effectiveWorkerSelection.worker?.provider ?? effectiveWorkerSelection.flash?.provider ?? null;
400
- const fallbackReadinessMatrix = buildConfigReadinessMatrix({
401
- hubCompatibility,
402
- workerProviderMode,
403
- providerCatalogChecked,
404
- providerCatalogBody,
405
- });
406
- const readinessMatrix = hubReadinessSnapshot?.readiness_matrix ?? fallbackReadinessMatrix;
407
- const roleProfiles = loadRoleProfiles();
408
- const workspaceReadiness = await assessWorkspaceReadiness({ cwd: process.cwd() });
409
- const enabledRoles = {
410
- worker: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false && globalConfig.worker_state !== 'disabled',
411
- reviewer: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false && globalConfig.review_state !== 'disabled',
412
- };
413
- const readinessSnapshot = hubReadinessSnapshot
414
- ? (() => {
415
- const snapshot = {
416
- ...structuredClone(hubReadinessSnapshot),
417
- readiness_matrix: readinessMatrix,
418
- workspace: {
419
- status: workspaceReadiness.status ?? (workspaceReadiness.ok === true ? 'READY' : 'UNAVAILABLE'),
420
- reason_code: workspaceReadiness.reason_code ?? workspaceReadiness.code ?? 'WORKSPACE_NOT_CHECKED',
421
- },
422
- };
423
- // `reprojectRuntimeModelCallability` takes `enabled_roles`; the local
424
- // binding is camelCase. Naming the property matters: a bare
425
- // `{ enabled_roles }` shorthand is a ReferenceError, and it fires on
426
- // every report whenever the Hub returns a readiness snapshot, which is
427
- // the normal case.
428
- const modelCallability = reprojectRuntimeModelCallability(snapshot, { enabled_roles: enabledRoles });
429
- return modelCallability ? { ...snapshot, model_callability: modelCallability } : snapshot;
430
- })()
431
- : buildRuntimeReadinessSnapshot({
432
- runtime: getHubRuntimeIdentity(),
433
- readinessMatrix,
434
- selections: { worker: effectiveWorkerSelection.worker ?? effectiveWorkerSelection.flash, reviewer: effectiveWorkerSelection.reviewer ?? effectiveWorkerSelection.pro },
435
- health_status: 'UNAVAILABLE',
436
- workspace: workspaceReadiness,
437
- enabled_roles: enabledRoles,
438
- });
439
- const extensionRuntime = hubExtension?.runtime ?? getHubRuntimeIdentity();
440
- const extensionContract = buildExtensionContract({
441
- config: {
442
- ...globalConfig,
443
- subagents_enabled: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false,
444
- worker_state: globalConfig.worker_state,
445
- review_state: globalConfig.review_state,
446
- escalate_on_failure: sessionConfig.escalate_on_failure ?? legacy.escalate_on_failure,
447
- },
448
- readinessMatrix,
449
- readinessSnapshot,
450
- workspace: workspaceReadiness,
451
- profiles: roleProfiles,
452
- runtime: extensionRuntime,
453
- });
454
- return {
455
- enabled: sessionConfig.enabled,
456
- default_tier: sessionConfig.default_tier ?? legacy.default_tier,
457
- default_effort: sessionConfig.default_effort ?? legacy.default_effort,
458
- mode: sessionConfig.mode ?? legacy.mode,
459
- default_timeout_seconds: sessionConfig.default_timeout_seconds ?? legacy.default_timeout_seconds,
460
- tier_policy: sessionConfig.tier_policy ?? legacy.tier_policy,
461
- escalate_on_failure: sessionConfig.escalate_on_failure ?? legacy.escalate_on_failure,
462
- preset_flash: sessionConfig.preset_flash ?? legacy.preset_flash,
463
- preset_pro: sessionConfig.preset_pro ?? legacy.preset_pro,
464
- worker_provider_mode: workerProviderMode,
465
- effective_worker_provider: effectiveWorkerProvider,
466
- effective_worker_selection: effectiveWorkerSelection,
467
- provider_resolution_error: providerResolutionError,
468
- flash_model_priority: globalConfig.flash_model_priority ?? [],
469
- flash_model_fallback: globalConfig.flash_model_fallback ?? 'harness-default',
470
- pro_model_priority: globalConfig.pro_model_priority ?? [],
471
- pro_model_fallback: globalConfig.pro_model_fallback ?? 'harness-default',
472
- subagents_enabled: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false,
473
- collaboration_mode: collaborationMode,
474
- main_agent_mode: mainAgentMode,
475
- flash_state: flashState,
476
- pro_state: proState,
477
- flash_roles: globalConfig.flash_roles ?? [],
478
- pro_roles: globalConfig.pro_roles ?? [],
479
- pro_reviews_flash: sessionConfig.pro_reviews_flash ?? shouldAutoReview(globalConfig, sessionConfig),
480
- effective_policy: `mode=${collaborationMode} flash=${flashState} pro=${proState} subagents=${sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false}`,
481
- legacy_source: sessionConfig.tier_policy !== undefined
482
- ? `session tier_policy clamp (${sessionConfig.tier_policy})`
483
- : `global collaboration mode (${collaborationMode})`,
484
- effective_default_tier: defaultDecision.ok ? defaultDecision.tier : null,
485
- effective_default_tier_reason: defaultDecision.ok ? defaultDecision.guidance : (defaultDecision.error.policyCode ?? 'none'),
486
- routing_guidance: getRoutingGuidance(globalConfig, sessionConfig),
487
- session_overrides: overrides,
488
- global_defaults: Object.fromEntries(SAFE_GLOBAL_KEYS.map((k) => [k, globalConfig[k]])),
489
- runtime_controls: runtimeControls,
490
- activation_boundaries: activationBoundaries,
491
- readiness_matrix: readinessMatrix,
492
- readiness_snapshot: readinessSnapshot,
493
- role_profiles: roleProfiles,
494
- extension_contract: extensionContract,
495
- hub_reachable: hubCompatibility.reachable,
496
- hub_compatible: hubCompatibility.compatible,
497
- hub_compatibility: hubCompatibility,
498
- };
499
- }
500
-
501
- server.registerTool('dsh_spawn_worker', {
502
- title: 'Spawn DSH worker (async)',
503
- description: 'Start a DSH (DeepSeek Harness) coding workflow in the background and return immediately with a workflow id. Use dsh_worker_status / dsh_worker_result to follow up. Role policy is enforced exactly like dsh_run_worker, and the workflow (verification, escalation, automatic reviewer pass) runs exactly the same for async jobs — only the caller does not await.',
504
- inputSchema: {
505
- task: z.string(),
506
- role: roleSchema,
507
- tier: tierSchema,
508
- legacy_tier: tierSchema.describe('Legacy model-class hint (flash | pro) — only influences the model class backing a worker role, never the role gate'),
509
- effort: effortSchema,
510
- cwd: z.string().optional(),
511
- job_id: clientJobIdSchema,
512
- workspace: workspaceSchema,
513
- constraints: constraintsSchema,
514
- profile: profileSchema,
515
- workspace_id: workspaceIdSchema,
516
- context_refs: contextRefsSchema,
517
- },
518
- }, async ({ task, role, tier, legacy_tier, effort, cwd, profile, workspace_id, context_refs, job_id, workspace, constraints }) => {
519
- if (!sessionConfig.enabled) return dispatchDisabled();
520
- const prepared = prepareDispatch({ task, role, tier, legacy_tier, effort, cwd, profile, workspace_id, context_refs, job_id, workspace, constraints });
521
- if (!prepared.ok) return prepared.response;
522
- const wf = workflowRuntime.start(prepared.spec);
523
- return text({ ...workflowRuntime.get(wf.id), workflow_id: wf.id, note: 'started in background; poll with dsh_worker_status / dsh_worker_result' });
524
- });
525
-
526
- server.registerTool('dsh_worker_status', {
527
- title: 'DSH worker status',
528
- description: 'List all DSH worker workflows in this session with phase, role, attempt, current model and token usage.',
529
- inputSchema: {},
530
- }, async () => {
531
- return text(workflowRuntime.list());
532
- });
533
-
534
- server.registerTool('dsh_worker_result', {
535
- title: 'DSH worker result',
536
- description: 'Fetch the result of a worker workflow, optionally waiting for it to finish. Accepts workflow ids (wf-...), Hub attempt ids (hub-...) and legacy standalone ids (job-...).',
537
- inputSchema: {
538
- job_id: z.string(),
539
- wait_seconds: z.number().int().min(0).max(7200).default(0).describe('0 = return current state immediately'),
540
- after_sequence: z.number().int().min(0).default(0).describe('Return canonical events after this cursor for incremental watch.'),
541
- detail: detailSchema,
542
- },
543
- }, async ({ job_id, wait_seconds, after_sequence, detail }) => {
544
- if (job_id.startsWith('wf-')) {
545
- if (wait_seconds > 0) await workflowRuntime.wait(job_id, wait_seconds * 1000);
546
- const view = workflowRuntime.get(job_id, { withResult: true });
547
- if (!view) return text({ error: `no such workflow: ${job_id}` });
548
- return text(projectWorkflowView(view, { detail, afterSequence: after_sequence }));
549
- }
550
- if (job_id.startsWith('hub-')) {
551
- const status = await hubStatus();
552
- if (!status.compatible) return text({ error: hubCompatibilityMessage(status), code: status.code, hub_compatibility: status });
553
- const view = await hub.get(job_id, wait_seconds).catch((e) => ({ error: e.message }));
554
- return text(projectWorkflowView(view, { detail, afterSequence: after_sequence }));
555
- }
556
- if (!getJob(job_id)) return text({ error: `no such job: ${job_id} (expected a wf- workflow id)` });
557
- const job = await waitJob(job_id, wait_seconds > 0 ? wait_seconds * 1000 : 1);
558
- return text(projectWorkflowView(jobView(job, { withResult: true }), { detail, afterSequence: after_sequence }));
559
- });
560
-
561
- server.registerTool('dsh_worker_cancel', {
562
- title: 'Cancel DSH worker',
563
- description: 'Cancel a worker workflow (stops the active attempt, never starts escalation/review, releases its worktree). Accepts workflow ids (wf-...), Hub attempt ids (hub-...) and legacy standalone ids (job-...).',
564
- inputSchema: { job_id: z.string(), detail: detailSchema },
565
- }, async ({ job_id, detail }) => {
566
- if (job_id.startsWith('wf-')) {
567
- const view = await workflowRuntime.cancel(job_id);
568
- if (!view) return text({ error: `no such workflow: ${job_id}` });
569
- return text({ ...projectWorkflowView(view, { detail }), note: 'cancelled' });
570
- }
571
- if (job_id.startsWith('hub-')) {
572
- const status = await hubStatus();
573
- if (!status.compatible) return text({ error: hubCompatibilityMessage(status), code: status.code, hub_compatibility: status });
574
- const view = await hub.cancel(job_id).catch((e) => ({ error: e.message }));
575
- return text(projectWorkflowView(view, { detail }));
576
- }
577
- if (!getJob(job_id)) return text({ error: `no such job: ${job_id} (expected a wf- workflow id)` });
578
- return text(projectWorkflowView(jobView(await cancelJob(job_id), { withResult: true }), { detail }));
579
- });
580
-
581
- await server.connect(new StdioServerTransport());
1
+ // MCP stdio server exposing the DSH worker pool to Claude Code / Codex.
2
+
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { z } from 'zod';
6
+ import { startJob, waitJob, cancelJob, listJobs, getJob, jobView } from './jobs.mjs';
7
+ import { hubStatus, hub } from './hub-client.mjs';
8
+ import { hubCompatibilityMessage, resolveHubExecutionMode } from './hub-compatibility.mjs';
9
+ import { RUNTIME_VERSION, getHubRuntimeIdentity } from './runtime-identity.mjs';
10
+ import { resolveWorkerModel } from './model-routing.mjs';
11
+ import { runtimeActivationMetadata } from './runtime-controls.mjs';
12
+ import { buildConfigReadinessMatrix } from './config-readiness.mjs';
13
+ import { buildRuntimeReadinessSnapshot, reprojectRuntimeModelCallability } from './runtime-readiness-snapshot.mjs';
14
+ import { classifyFailure, classifyFailureCode } from './failure-classification.mjs';
15
+ import {
16
+ normalizeGlobalConfig,
17
+ deriveLegacyConfig,
18
+ getEffectiveTierState,
19
+ getRoutingGuidance,
20
+ chooseDefaultTier,
21
+ canDispatchRole,
22
+ resolveRoleTierHint,
23
+ shouldAutoReview,
24
+ } from './policy.mjs';
25
+ import { buildMcpWorkflowRuntime } from './mcp-runtime.mjs';
26
+ import { buildReviewTask } from './information-flow.mjs';
27
+ import { projectWorkflowView } from './job-contracts.mjs';
28
+ import { loadRoleProfiles, resolveRoleProfile } from './role-profiles.mjs';
29
+ import { loadWorkspaceContexts, resolveWorkspaceContext, buildWorkspaceTask, addContextReferences, isSafeBranchName } from './workspace-context.mjs';
30
+ import { buildExtensionContract } from './extension-contract.mjs';
31
+ import { assessWorkspaceReadiness } from './workspace-readiness.mjs';
32
+ import { detectOrchestrator } from './orchestrator.mjs';
33
+
34
+ const server = new McpServer({ name: 'dsh-crew', version: RUNTIME_VERSION });
35
+
36
+ const tierSchema = z.enum(['flash', 'pro']).optional().describe('Legacy worker tier (compatibility only): Flash/Pro now act as a model-class hint, not a role. Prefer role=worker / role=reviewer; the backend resolves the actual provider/model from the Model Policy.');
37
+ const roleSchema = z.enum(['worker', 'reviewer']).optional().describe('Dispatch role: worker executes implementation / fixes / tests / search; reviewer independently reviews a completed implementation. A coding request defaults to worker; reviewer runs on explicit request or via the automatic review workflow.');
38
+ const effortSchema = z.enum(['off', 'high', 'max']).optional().describe('Reasoning effort for the worker. Omit to use the session default.');
39
+ const detailSchema = z.enum(['compact', 'full']).default('compact').describe('compact returns the bounded Result Contract; full explicitly includes raw workflow/candidate details for debugging.');
40
+ const profileSchema = z.string().max(64).optional().describe('Versioned Worker/Reviewer profile id; defaults by role.');
41
+ const workspaceIdSchema = z.string().max(64).optional().describe('Workspace Context id from Crew-owned workspaces.json.');
42
+ const contextRefsSchema = z.array(z.string().max(256)).max(32).optional().describe('Additional workspace-relative instruction references; contents are not copied.');
43
+ const clientJobIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/).optional().describe('Optional caller id echoed in events and evidence; Crew still assigns its own workflow id.');
44
+ const workspaceSchema = z.object({
45
+ repo_root: z.string().optional(),
46
+ branch: z.string().refine(isSafeBranchName, 'invalid git branch name').optional(),
47
+ worktree: z.enum(['auto', 'existing', 'none']).optional(),
48
+ }).optional().describe('Per-job workspace overrides. auto isolates coding Workers; existing/none use the supplied workspace.');
49
+ const constraintsSchema = z.object({
50
+ timeout_seconds: z.number().int().positive().max(7200).optional(),
51
+ allow_fallback: z.boolean().optional(),
52
+ allow_no_changes: z.boolean().optional().describe('Explicitly allow a verified read-only or analysis job to succeed with zero workspace changes.'),
53
+ }).optional().describe('Per-job constraints override profile and session defaults.');
54
+
55
+ // Session-level configuration. This MCP server process lives exactly as long
56
+ // as one Claude Code / Codex session, so plain memory IS session scope.
57
+ // Initial values come from the global config (~/.config/dsh-crew/config.json,
58
+ // edited on the DSH settings page); dsh_worker_config overrides per session.
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.
63
+ import { readGlobalConfig } from './install/install.mjs';
64
+ const initialGlobalConfig = normalizeGlobalConfig(readGlobalConfig());
65
+ const legacyDefaults = deriveLegacyConfig(initialGlobalConfig);
66
+ const currentGlobalConfig = () => normalizeGlobalConfig(readGlobalConfig());
67
+ const sessionConfig = {
68
+ enabled: true,
69
+ default_tier: legacyDefaults.default_tier,
70
+ default_effort: legacyDefaults.default_effort,
71
+ mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode, // auto | hub; legacy standalone is migrated to hub
72
+ default_timeout_seconds: legacyDefaults.default_timeout_seconds,
73
+ tier_policy: undefined,
74
+ escalate_on_failure: legacyDefaults.escalate_on_failure,
75
+ preset_flash: legacyDefaults.preset_flash ?? 'default',
76
+ preset_pro: legacyDefaults.preset_pro ?? 'default',
77
+ collaboration_mode: undefined,
78
+ main_agent_mode: undefined,
79
+ flash_state: undefined,
80
+ pro_state: undefined,
81
+ pro_reviews_flash: undefined,
82
+ };
83
+
84
+ function resetSessionConfig() {
85
+ Object.assign(sessionConfig, {
86
+ enabled: true,
87
+ default_tier: legacyDefaults.default_tier,
88
+ default_effort: legacyDefaults.default_effort,
89
+ mode: legacyDefaults.mode === 'standalone' ? 'hub' : legacyDefaults.mode,
90
+ default_timeout_seconds: legacyDefaults.default_timeout_seconds,
91
+ tier_policy: undefined,
92
+ escalate_on_failure: legacyDefaults.escalate_on_failure,
93
+ preset_flash: legacyDefaults.preset_flash ?? 'default',
94
+ preset_pro: legacyDefaults.preset_pro ?? 'default',
95
+ collaboration_mode: undefined,
96
+ main_agent_mode: undefined,
97
+ flash_state: undefined,
98
+ pro_state: undefined,
99
+ pro_reviews_flash: undefined,
100
+ });
101
+ }
102
+
103
+ function presetForTier(tier) {
104
+ const p = tier === 'flash' ? sessionConfig.preset_flash : sessionConfig.preset_pro;
105
+ return !p || p === 'default' ? undefined : p;
106
+ }
107
+
108
+ function text(obj) {
109
+ let payload = obj;
110
+ if (obj && typeof obj === 'object' && !Array.isArray(obj) && obj.failure === undefined) {
111
+ if (obj.code) {
112
+ payload = { ...obj, failure: classifyFailureCode(obj.code) };
113
+ } else if (obj.status !== undefined || obj.phase !== undefined || obj.outcome !== undefined || obj.error_code !== undefined) {
114
+ payload = {
115
+ ...obj,
116
+ failure: classifyFailure({
117
+ phase: obj.phase,
118
+ status: obj.status,
119
+ errorCode: obj.error_code,
120
+ outcome: obj.outcome,
121
+ decision: obj.decision,
122
+ review: obj.review,
123
+ childAttempts: obj.child_attempts,
124
+ }),
125
+ };
126
+ }
127
+ }
128
+ return { content: [{ type: 'text', text: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2) }] };
129
+ }
130
+
131
+ const ORCHESTRATOR = detectOrchestrator();
132
+
133
+ function policyRejection(decision) {
134
+ return text({
135
+ error: decision.error.message,
136
+ code: decision.error.policyCode,
137
+ note: 'DSH Crew policy rejection — report this to the user instead of doing the task yourself.',
138
+ });
139
+ }
140
+
141
+ function dispatchDisabled() {
142
+ return text({
143
+ error: 'worker dispatch is disabled for this session (set via dsh_worker_config). Report this to the user instead of doing the task yourself.',
144
+ code: 'SUBAGENTS_DISABLED',
145
+ });
146
+ }
147
+
148
+ async function resolveMode() {
149
+ const status = await hubStatus();
150
+ const decision = resolveHubExecutionMode(sessionConfig.mode, status, { productionOnly: true });
151
+ if (!decision.ok) {
152
+ throw Object.assign(new Error(decision.error), {
153
+ code: decision.code,
154
+ hubStatus: status,
155
+ });
156
+ }
157
+ return decision.mode;
158
+ }
159
+
160
+ const workflowRuntime = buildMcpWorkflowRuntime({
161
+ getSessionConfig: () => sessionConfig,
162
+ resolveMode,
163
+ presetForTier,
164
+ readGlobalConfig,
165
+ buildReviewTask,
166
+ attemptTimeoutMs: () => (sessionConfig.default_timeout_seconds ?? 1800) * 1000,
167
+ });
168
+
169
+ function dispatchError(code, details = {}) {
170
+ return { ok: false, response: text({ error: details.error ?? code, code, ...details }) };
171
+ }
172
+
173
+ function prepareDispatch({ task, role, tier, legacy_tier, effort, cwd, timeout_seconds, profile, workspace_id, context_refs, job_id, workspace: workspaceOverride, constraints }) {
174
+ const globalConfig = currentGlobalConfig();
175
+ const profileRegistry = loadRoleProfiles();
176
+ if (!profileRegistry.ok) return dispatchError('PROFILE_FILE_INVALID', { errors: profileRegistry.errors });
177
+ const profileRole = profile ? profileRegistry.profiles?.[profile]?.role : undefined;
178
+ const requestedRole = role ?? profileRole;
179
+ const hint = resolveRoleTierHint(requestedRole, legacy_tier ?? tier);
180
+ if (!hint.ok) return dispatchError(hint.code, { error: hint.error });
181
+ const effRole = hint.role;
182
+ const resolvedProfile = resolveRoleProfile(profileRegistry, profile, effRole);
183
+ if (!resolvedProfile.ok) return dispatchError(resolvedProfile.code, resolvedProfile);
184
+
185
+ let effTier;
186
+ let decision;
187
+ if (requestedRole === undefined) {
188
+ decision = chooseDefaultTier(globalConfig, legacy_tier ?? tier, sessionConfig);
189
+ if (!decision.ok) return { ok: false, response: policyRejection(decision) };
190
+ effTier = decision.tier;
191
+ } else {
192
+ decision = canDispatchRole(globalConfig, effRole, true, sessionConfig);
193
+ if (!decision.ok) return { ok: false, response: policyRejection(decision) };
194
+ if (effRole === 'reviewer') effTier = 'pro';
195
+ else if (legacy_tier !== undefined) effTier = legacy_tier;
196
+ else if (tier !== undefined) effTier = tier;
197
+ else { const slot = chooseDefaultTier(globalConfig, undefined, sessionConfig); effTier = slot.ok ? slot.tier : 'flash'; }
198
+ }
199
+
200
+ const workDir = workspaceOverride?.repo_root ?? cwd ?? process.cwd();
201
+ const workspace = resolveWorkspaceContext(loadWorkspaceContexts(), { workspace_id, cwd: workDir });
202
+ if (!workspace.ok) return dispatchError(workspace.code, workspace);
203
+ const withRefs = addContextReferences(workspace.context, context_refs, { cwd: workDir });
204
+ if (!withRefs.ok) return dispatchError(withRefs.code);
205
+ const profileValue = resolvedProfile.profile;
206
+ const effectiveTimeout = constraints?.timeout_seconds ?? timeout_seconds ?? profileValue.timeout_seconds ?? sessionConfig.default_timeout_seconds;
207
+ const requestedIsolation = workspaceOverride?.worktree === 'auto'
208
+ ? 'worktree'
209
+ : workspaceOverride?.worktree === 'existing' || workspaceOverride?.worktree === 'none'
210
+ ? 'shared'
211
+ : profileValue.isolation;
212
+ return {
213
+ ok: true,
214
+ role: effRole,
215
+ timeout: effectiveTimeout,
216
+ spec: {
217
+ client_job_id: job_id ?? null,
218
+ role: effRole,
219
+ delivery: effRole === 'reviewer' ? 'review' : 'coding',
220
+ model_class_hint: effTier,
221
+ task: buildWorkspaceTask(task, withRefs.context),
222
+ cwd: workDir,
223
+ effort: effort ?? sessionConfig.default_effort,
224
+ source: ORCHESTRATOR,
225
+ profile_id: resolvedProfile.profile_id,
226
+ requested_isolation: requestedIsolation,
227
+ workspace_branch: workspaceOverride?.branch ?? withRefs.context?.default_branch ?? null,
228
+ timeout_seconds: effectiveTimeout,
229
+ allow_fallback: constraints?.allow_fallback ?? profileValue.fallback,
230
+ allow_no_changes: constraints?.allow_no_changes === true,
231
+ routing: profileValue.routing,
232
+ review_strictness: profileValue.review_strictness,
233
+ workspace_context: withRefs.context,
234
+ },
235
+ };
236
+ }
237
+
238
+ server.registerTool('dsh_run_worker', {
239
+ title: 'Run DSH worker (blocking)',
240
+ description: 'Delegate a task to a DSH (DeepSeek Harness) coding agent and wait for its final result. The worker is a full DSH agent with its own tools and sandbox. Pass role=worker for implementation and role=reviewer for an independent review pass. Disabled roles are refused by the DSH Crew policy; Manual roles only run when explicitly requested. Blocks until the worker finishes.',
241
+ inputSchema: {
242
+ task: z.string().describe('Full task description for the worker, self-contained'),
243
+ role: roleSchema,
244
+ tier: tierSchema,
245
+ legacy_tier: tierSchema.describe('Legacy model-class hint (flash | pro), forwarded verbatim by deprecated ds-flash/ds-pro aliases; only influences which model class backs a worker role, never the role gate'),
246
+ effort: effortSchema,
247
+ cwd: z.string().optional().describe('Workspace directory for the worker (defaults to current project)'),
248
+ timeout_seconds: z.number().int().positive().max(7200).optional(),
249
+ job_id: clientJobIdSchema,
250
+ workspace: workspaceSchema,
251
+ constraints: constraintsSchema,
252
+ profile: profileSchema,
253
+ workspace_id: workspaceIdSchema,
254
+ context_refs: contextRefsSchema,
255
+ detail: detailSchema,
256
+ },
257
+ }, async ({ task, role, tier, legacy_tier, effort, cwd, timeout_seconds, profile, workspace_id, context_refs, job_id, workspace, constraints, detail }) => {
258
+ if (!sessionConfig.enabled) return dispatchDisabled();
259
+ const prepared = prepareDispatch({ task, role, tier, legacy_tier, effort, cwd, timeout_seconds, profile, workspace_id, context_refs, job_id, workspace, constraints });
260
+ if (!prepared.ok) return prepared.response;
261
+ const wf = workflowRuntime.start(prepared.spec);
262
+ await workflowRuntime.wait(wf.id, prepared.timeout * 1000);
263
+ const view = workflowRuntime.get(wf.id, { withResult: true });
264
+ if (view.status === 'running') {
265
+ return text({ ...projectWorkflowView(view, { detail }), note: `still running after ${prepared.timeout}s; poll with dsh_worker_result`, status: 'running' });
266
+ }
267
+ if (view.phase === 'failed') {
268
+ return text({ ...projectWorkflowView(view, { detail }), note: 'workflow failed — see error / error_code.', status: 'failed' });
269
+ }
270
+ return text({ ...projectWorkflowView(view, { detail }), note: prepared.role === 'reviewer' ? 'review complete' : 'workflow complete' });
271
+ });
272
+
273
+ server.registerTool('dsh_worker_config', {
274
+ title: 'Session worker configuration',
275
+ description: 'Read or update session-level worker settings: enable/disable dispatch, default tier/effort/timeout, execution mode, tier policy, escalation, collaboration mode, per-tier state and review behavior. Call with no arguments to read the current configuration, runtime control state, activation boundaries, global defaults, session overrides, effective policy and routing guidance. Settings last for this session only.',
276
+ inputSchema: {
277
+ enabled: z.boolean().optional().describe('false = refuse all worker dispatch this session'),
278
+ default_tier: z.enum(['flash', 'pro']).optional(),
279
+ default_effort: z.enum(['off', 'high', 'max']).optional(),
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
+ default_timeout_seconds: z.number().int().positive().max(7200).optional(),
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
+ escalate_on_failure: z.boolean().optional().describe('allow an unverified worker attempt to retry through the stronger model policy (applies to run and spawn)'),
284
+ preset_flash: z.string().optional().describe('hub-mode agent preset for flash workers (preset id, or "default")'),
285
+ preset_pro: z.string().optional().describe('hub-mode agent preset for pro workers (preset id, or "default")'),
286
+ collaboration_mode: z.enum(['flash-only', 'pro-only', 'balanced', 'review-pipeline', 'custom']).optional().describe('session override of the global collaboration mode'),
287
+ main_agent_mode: z.enum(['direct-allowed', 'coordinator-first', 'dispatcher-only']).optional().describe('session override of the host routing guidance'),
288
+ flash_state: z.enum(['disabled', 'manual', 'auto']).optional().describe('session override of the flash tier state (custom mode)'),
289
+ pro_state: z.enum(['disabled', 'manual', 'auto']).optional().describe('session override of the pro tier state (custom mode)'),
290
+ pro_reviews_flash: z.boolean().optional().describe('enable the automatic reviewer after a verified worker workflow (applies to run and spawn)'),
291
+ reset: z.boolean().optional().describe('true = restore all defaults first'),
292
+ },
293
+ }, async ({ reset, ...patch }) => {
294
+ if (reset) resetSessionConfig();
295
+ if (patch.tier_policy === 'auto') {
296
+ sessionConfig.tier_policy = undefined;
297
+ sessionConfig.collaboration_mode = 'balanced';
298
+ delete patch.tier_policy;
299
+ }
300
+ for (const [k, v] of Object.entries(patch)) if (v !== undefined) sessionConfig[k] = v;
301
+ return text(await buildConfigReport());
302
+ });
303
+
304
+ const SAFE_GLOBAL_KEYS = [
305
+ 'default_tier', 'default_effort', 'mode', 'default_timeout_seconds', 'hub_url',
306
+ 'tier_policy', 'escalate_on_failure', 'subagents_enabled', 'collaboration_mode',
307
+ 'main_agent_mode', 'flash_state', 'pro_state', 'flash_roles', 'pro_roles',
308
+ 'pro_reviews_flash', 'worker_provider_mode',
309
+ 'flash_model_priority', 'flash_model_priority_configured', 'flash_model_fallback',
310
+ 'pro_model_priority', 'pro_model_priority_configured', 'pro_model_fallback',
311
+ 'preset_flash', 'preset_pro',
312
+ ];
313
+
314
+ function sameHubRuntimeIdentity(status, extension, snapshot) {
315
+ const runtime = extension?.runtime;
316
+ const projected = snapshot?.runtime;
317
+ return status?.compatible === true
318
+ && extension?.schema_version === 1
319
+ && extension?.kind === 'dsh-crew-extension'
320
+ && snapshot?.schema_version === 1
321
+ && runtime?.runtime_id === status.runtime_id
322
+ && runtime?.protocol_version === status.protocol_version
323
+ && runtime?.execution_plane === status.execution_plane
324
+ && runtime?.profile === status.profile
325
+ && runtime?.listen_port === status.listen_port
326
+ && projected?.runtime_id === runtime.runtime_id
327
+ && projected?.execution_plane === runtime.execution_plane
328
+ && projected?.profile === runtime.profile
329
+ && projected?.listen_port === runtime.listen_port;
330
+ }
331
+
332
+ async function buildConfigReport() {
333
+ const globalConfig = currentGlobalConfig();
334
+ const legacy = deriveLegacyConfig(globalConfig);
335
+ const flashState = getEffectiveTierState(globalConfig, 'flash', sessionConfig);
336
+ const proState = getEffectiveTierState(globalConfig, 'pro', sessionConfig);
337
+ const defaultDecision = chooseDefaultTier(globalConfig, undefined, sessionConfig);
338
+ const overrides = {};
339
+ for (const [k, v] of Object.entries(sessionConfig)) if (v !== undefined) overrides[k] = v;
340
+ const collaborationMode = sessionConfig.collaboration_mode ?? globalConfig.collaboration_mode;
341
+ const mainAgentMode = sessionConfig.main_agent_mode ?? globalConfig.main_agent_mode;
342
+ const runtimeControls = workflowRuntime.refreshRuntimeControls();
343
+ const activationBoundaries = globalConfig.config_activation ?? runtimeActivationMetadata();
344
+ const hubCompatibility = await hubStatus({ force: true, base: globalConfig.hub_url });
345
+ let effectiveWorkerProvider = null;
346
+ let effectiveWorkerSelection = { flash: null, pro: null };
347
+ let providerResolutionError;
348
+ let providerCatalogChecked = false;
349
+ let providerCatalogBody = null;
350
+ let hubExtension = null;
351
+ let hubReadinessSnapshot = null;
352
+ const workerProviderMode = globalConfig.worker_provider_mode ?? 'deepseek-official';
353
+ if (hubCompatibility.compatible) {
354
+ try {
355
+ const extensionRes = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/extension`, { signal: AbortSignal.timeout(800) });
356
+ const extensionBody = await extensionRes.json();
357
+ const candidateExtension = extensionBody?.extension;
358
+ const candidateSnapshot = candidateExtension?.readiness_snapshot;
359
+ if (extensionRes.ok && extensionBody?.ok === true && sameHubRuntimeIdentity(hubCompatibility, candidateExtension, candidateSnapshot)) {
360
+ hubExtension = candidateExtension;
361
+ hubReadinessSnapshot = candidateSnapshot;
362
+ }
363
+ } catch { /* canonical Hub readiness remains unavailable; fallback stays conservative */ }
364
+ }
365
+ if (workerProviderMode === 'deepseek-official') {
366
+ effectiveWorkerSelection = {
367
+ flash: { provider: 'deepseek-official', model: 'deepseek-v4-flash', source: 'legacy-strict' },
368
+ pro: { provider: 'deepseek-official', model: 'deepseek-v4-pro', source: 'legacy-strict' },
369
+ };
370
+ } else if (hubCompatibility.compatible) {
371
+ try {
372
+ providerCatalogChecked = true;
373
+ const res = await fetch(`${globalConfig.hub_url}/_dsh/dsh-crew/models`, { signal: AbortSignal.timeout(800) });
374
+ const body = await res.json();
375
+ providerCatalogBody = body;
376
+ if (!body?.ok) providerResolutionError = body?.error ?? 'Unable to read Harness model catalog.';
377
+ else for (const tier of ['flash', 'pro']) {
378
+ const selected = resolveWorkerModel({
379
+ tier,
380
+ priority: globalConfig[`${tier}_model_priority`],
381
+ priorityConfigured: globalConfig[`${tier}_model_priority_configured`],
382
+ fallback: globalConfig[`${tier}_model_fallback`],
383
+ catalog: body,
384
+ harnessDefault: body.harness_default,
385
+ });
386
+ effectiveWorkerSelection[tier] = selected.ok
387
+ ? { provider: selected.provider, model: selected.model, source: selected.source }
388
+ : { code: selected.code, error: selected.message };
389
+ }
390
+ } catch (err) {
391
+ providerCatalogChecked = true;
392
+ providerResolutionError = err?.message ?? String(err);
393
+ }
394
+ } else if (hubCompatibility.reachable) {
395
+ providerResolutionError = hubCompatibilityMessage(hubCompatibility);
396
+ }
397
+ if (hubReadinessSnapshot?.worker?.selected) effectiveWorkerSelection.worker = hubReadinessSnapshot.worker.selected;
398
+ if (hubReadinessSnapshot?.reviewer?.selected) effectiveWorkerSelection.reviewer = hubReadinessSnapshot.reviewer.selected;
399
+ effectiveWorkerProvider = effectiveWorkerSelection.worker?.provider ?? effectiveWorkerSelection.flash?.provider ?? null;
400
+ const fallbackReadinessMatrix = buildConfigReadinessMatrix({
401
+ hubCompatibility,
402
+ workerProviderMode,
403
+ providerCatalogChecked,
404
+ providerCatalogBody,
405
+ });
406
+ const readinessMatrix = hubReadinessSnapshot?.readiness_matrix ?? fallbackReadinessMatrix;
407
+ const roleProfiles = loadRoleProfiles();
408
+ const workspaceReadiness = await assessWorkspaceReadiness({ cwd: process.cwd() });
409
+ const enabledRoles = {
410
+ worker: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false && globalConfig.worker_state !== 'disabled',
411
+ reviewer: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false && globalConfig.review_state !== 'disabled',
412
+ };
413
+ const readinessSnapshot = hubReadinessSnapshot
414
+ ? (() => {
415
+ const snapshot = {
416
+ ...structuredClone(hubReadinessSnapshot),
417
+ readiness_matrix: readinessMatrix,
418
+ workspace: {
419
+ status: workspaceReadiness.status ?? (workspaceReadiness.ok === true ? 'READY' : 'UNAVAILABLE'),
420
+ reason_code: workspaceReadiness.reason_code ?? workspaceReadiness.code ?? 'WORKSPACE_NOT_CHECKED',
421
+ },
422
+ };
423
+ // `reprojectRuntimeModelCallability` takes `enabled_roles`; the local
424
+ // binding is camelCase. Naming the property matters: a bare
425
+ // `{ enabled_roles }` shorthand is a ReferenceError, and it fires on
426
+ // every report whenever the Hub returns a readiness snapshot, which is
427
+ // the normal case.
428
+ const modelCallability = reprojectRuntimeModelCallability(snapshot, { enabled_roles: enabledRoles });
429
+ return modelCallability ? { ...snapshot, model_callability: modelCallability } : snapshot;
430
+ })()
431
+ : buildRuntimeReadinessSnapshot({
432
+ runtime: getHubRuntimeIdentity(),
433
+ readinessMatrix,
434
+ selections: { worker: effectiveWorkerSelection.worker ?? effectiveWorkerSelection.flash, reviewer: effectiveWorkerSelection.reviewer ?? effectiveWorkerSelection.pro },
435
+ health_status: 'UNAVAILABLE',
436
+ workspace: workspaceReadiness,
437
+ enabled_roles: enabledRoles,
438
+ });
439
+ const extensionRuntime = hubExtension?.runtime ?? getHubRuntimeIdentity();
440
+ const extensionContract = buildExtensionContract({
441
+ config: {
442
+ ...globalConfig,
443
+ subagents_enabled: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false,
444
+ worker_state: globalConfig.worker_state,
445
+ review_state: globalConfig.review_state,
446
+ escalate_on_failure: sessionConfig.escalate_on_failure ?? legacy.escalate_on_failure,
447
+ },
448
+ readinessMatrix,
449
+ readinessSnapshot,
450
+ workspace: workspaceReadiness,
451
+ profiles: roleProfiles,
452
+ runtime: extensionRuntime,
453
+ });
454
+ return {
455
+ enabled: sessionConfig.enabled,
456
+ default_tier: sessionConfig.default_tier ?? legacy.default_tier,
457
+ default_effort: sessionConfig.default_effort ?? legacy.default_effort,
458
+ mode: sessionConfig.mode ?? legacy.mode,
459
+ default_timeout_seconds: sessionConfig.default_timeout_seconds ?? legacy.default_timeout_seconds,
460
+ tier_policy: sessionConfig.tier_policy ?? legacy.tier_policy,
461
+ escalate_on_failure: sessionConfig.escalate_on_failure ?? legacy.escalate_on_failure,
462
+ preset_flash: sessionConfig.preset_flash ?? legacy.preset_flash,
463
+ preset_pro: sessionConfig.preset_pro ?? legacy.preset_pro,
464
+ worker_provider_mode: workerProviderMode,
465
+ effective_worker_provider: effectiveWorkerProvider,
466
+ effective_worker_selection: effectiveWorkerSelection,
467
+ provider_resolution_error: providerResolutionError,
468
+ flash_model_priority: globalConfig.flash_model_priority ?? [],
469
+ flash_model_fallback: globalConfig.flash_model_fallback ?? 'harness-default',
470
+ pro_model_priority: globalConfig.pro_model_priority ?? [],
471
+ pro_model_fallback: globalConfig.pro_model_fallback ?? 'harness-default',
472
+ subagents_enabled: sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false,
473
+ collaboration_mode: collaborationMode,
474
+ main_agent_mode: mainAgentMode,
475
+ flash_state: flashState,
476
+ pro_state: proState,
477
+ flash_roles: globalConfig.flash_roles ?? [],
478
+ pro_roles: globalConfig.pro_roles ?? [],
479
+ pro_reviews_flash: sessionConfig.pro_reviews_flash ?? shouldAutoReview(globalConfig, sessionConfig),
480
+ effective_policy: `mode=${collaborationMode} flash=${flashState} pro=${proState} subagents=${sessionConfig.enabled !== false && globalConfig.subagents_enabled !== false}`,
481
+ legacy_source: sessionConfig.tier_policy !== undefined
482
+ ? `session tier_policy clamp (${sessionConfig.tier_policy})`
483
+ : `global collaboration mode (${collaborationMode})`,
484
+ effective_default_tier: defaultDecision.ok ? defaultDecision.tier : null,
485
+ effective_default_tier_reason: defaultDecision.ok ? defaultDecision.guidance : (defaultDecision.error.policyCode ?? 'none'),
486
+ routing_guidance: getRoutingGuidance(globalConfig, sessionConfig),
487
+ session_overrides: overrides,
488
+ global_defaults: Object.fromEntries(SAFE_GLOBAL_KEYS.map((k) => [k, globalConfig[k]])),
489
+ runtime_controls: runtimeControls,
490
+ activation_boundaries: activationBoundaries,
491
+ readiness_matrix: readinessMatrix,
492
+ readiness_snapshot: readinessSnapshot,
493
+ role_profiles: roleProfiles,
494
+ extension_contract: extensionContract,
495
+ hub_reachable: hubCompatibility.reachable,
496
+ hub_compatible: hubCompatibility.compatible,
497
+ hub_compatibility: hubCompatibility,
498
+ };
499
+ }
500
+
501
+ server.registerTool('dsh_spawn_worker', {
502
+ title: 'Spawn DSH worker (async)',
503
+ description: 'Start a DSH (DeepSeek Harness) coding workflow in the background and return immediately with a workflow id. Use dsh_worker_status / dsh_worker_result to follow up. Role policy is enforced exactly like dsh_run_worker, and the workflow (verification, escalation, automatic reviewer pass) runs exactly the same for async jobs — only the caller does not await.',
504
+ inputSchema: {
505
+ task: z.string(),
506
+ role: roleSchema,
507
+ tier: tierSchema,
508
+ legacy_tier: tierSchema.describe('Legacy model-class hint (flash | pro) — only influences the model class backing a worker role, never the role gate'),
509
+ effort: effortSchema,
510
+ cwd: z.string().optional(),
511
+ job_id: clientJobIdSchema,
512
+ workspace: workspaceSchema,
513
+ constraints: constraintsSchema,
514
+ profile: profileSchema,
515
+ workspace_id: workspaceIdSchema,
516
+ context_refs: contextRefsSchema,
517
+ },
518
+ }, async ({ task, role, tier, legacy_tier, effort, cwd, profile, workspace_id, context_refs, job_id, workspace, constraints }) => {
519
+ if (!sessionConfig.enabled) return dispatchDisabled();
520
+ const prepared = prepareDispatch({ task, role, tier, legacy_tier, effort, cwd, profile, workspace_id, context_refs, job_id, workspace, constraints });
521
+ if (!prepared.ok) return prepared.response;
522
+ const wf = workflowRuntime.start(prepared.spec);
523
+ return text({ ...workflowRuntime.get(wf.id), workflow_id: wf.id, note: 'started in background; poll with dsh_worker_status / dsh_worker_result' });
524
+ });
525
+
526
+ server.registerTool('dsh_worker_status', {
527
+ title: 'DSH worker status',
528
+ description: 'List all DSH worker workflows in this session with phase, role, attempt, current model and token usage.',
529
+ inputSchema: {},
530
+ }, async () => {
531
+ return text(workflowRuntime.list());
532
+ });
533
+
534
+ server.registerTool('dsh_worker_result', {
535
+ title: 'DSH worker result',
536
+ description: 'Fetch the result of a worker workflow, optionally waiting for it to finish. Accepts workflow ids (wf-...), Hub attempt ids (hub-...) and legacy standalone ids (job-...).',
537
+ inputSchema: {
538
+ job_id: z.string(),
539
+ wait_seconds: z.number().int().min(0).max(7200).default(0).describe('0 = return current state immediately'),
540
+ after_sequence: z.number().int().min(0).default(0).describe('Return canonical events after this cursor for incremental watch.'),
541
+ detail: detailSchema,
542
+ },
543
+ }, async ({ job_id, wait_seconds, after_sequence, detail }) => {
544
+ if (job_id.startsWith('wf-')) {
545
+ if (wait_seconds > 0) await workflowRuntime.wait(job_id, wait_seconds * 1000);
546
+ const view = workflowRuntime.get(job_id, { withResult: true });
547
+ if (!view) return text({ error: `no such workflow: ${job_id}` });
548
+ return text(projectWorkflowView(view, { detail, afterSequence: after_sequence }));
549
+ }
550
+ if (job_id.startsWith('hub-')) {
551
+ const status = await hubStatus();
552
+ if (!status.compatible) return text({ error: hubCompatibilityMessage(status), code: status.code, hub_compatibility: status });
553
+ const view = await hub.get(job_id, wait_seconds).catch((e) => ({ error: e.message }));
554
+ return text(projectWorkflowView(view, { detail, afterSequence: after_sequence }));
555
+ }
556
+ if (!getJob(job_id)) return text({ error: `no such job: ${job_id} (expected a wf- workflow id)` });
557
+ const job = await waitJob(job_id, wait_seconds > 0 ? wait_seconds * 1000 : 1);
558
+ return text(projectWorkflowView(jobView(job, { withResult: true }), { detail, afterSequence: after_sequence }));
559
+ });
560
+
561
+ server.registerTool('dsh_worker_cancel', {
562
+ title: 'Cancel DSH worker',
563
+ description: 'Cancel a worker workflow (stops the active attempt, never starts escalation/review, releases its worktree). Accepts workflow ids (wf-...), Hub attempt ids (hub-...) and legacy standalone ids (job-...).',
564
+ inputSchema: { job_id: z.string(), detail: detailSchema },
565
+ }, async ({ job_id, detail }) => {
566
+ if (job_id.startsWith('wf-')) {
567
+ const view = await workflowRuntime.cancel(job_id);
568
+ if (!view) return text({ error: `no such workflow: ${job_id}` });
569
+ return text({ ...projectWorkflowView(view, { detail }), note: 'cancelled' });
570
+ }
571
+ if (job_id.startsWith('hub-')) {
572
+ const status = await hubStatus();
573
+ if (!status.compatible) return text({ error: hubCompatibilityMessage(status), code: status.code, hub_compatibility: status });
574
+ const view = await hub.cancel(job_id).catch((e) => ({ error: e.message }));
575
+ return text(projectWorkflowView(view, { detail }));
576
+ }
577
+ if (!getJob(job_id)) return text({ error: `no such job: ${job_id} (expected a wf- workflow id)` });
578
+ return text(projectWorkflowView(jobView(await cancelJob(job_id), { withResult: true }), { detail }));
579
+ });
580
+
581
+ await server.connect(new StdioServerTransport());