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