diff --git a/docs/context-runtime-local-dev.md b/docs/context-runtime-local-dev.md index 5aecdf9..bdffaf5 100644 --- a/docs/context-runtime-local-dev.md +++ b/docs/context-runtime-local-dev.md @@ -59,6 +59,9 @@ node scripts/check-mcp-compactor-local.mjs # codebase-memory-mcp CLI + Memind 索引(见 docs/codebase-memory-mcp-local.md) node scripts/check-codebase-memory-mcp-local.mjs + +# dsh CLI + dry-run launch plan(见 docs/dsh-executor-local.md,默认零 LLM) +node scripts/check-dsh-executor-local.mjs ``` ## 用户自测清单(有 LLM 配额时) @@ -86,6 +89,7 @@ ORDER BY created_at; ## 禁止项 - Agent **不得**自动设置 `GOOSE_V149_ALLOW_REAL_LLM=1` 或循环跑 phase2/phase3 +- **预计超过 10k token 的 LLM 调用须用户明确同意**(含 dsh headless / Goose smoke) - **禁止** `headroom wrap` / `headroom learn` / `HEADROOM_OUTPUT_SHAPER=1` - 页面生成 / Page Data 轮次首期 **排除** headroom active diff --git a/docs/dsh-executor-local.md b/docs/dsh-executor-local.md new file mode 100644 index 0000000..3cfefe0 --- /dev/null +++ b/docs/dsh-executor-local.md @@ -0,0 +1,83 @@ +# DeepSeek Harness (dsh) 本地开发 + +> MIT · developer preview · **并列 code executor,不替换 goosed** +> 关联:[context-runtime-harness-fusion-plan.md](./context-runtime-harness-fusion-plan.md) §6.3 + +## 用途边界 + +| 允许 | 禁止 | +|------|------| +| `tool-gateway` 白名单 task type 的 headless 代码任务 | 替换 Goose + `tkmind-proxy` 主路径 | +| 与 aider / openhands / cursor 并列 executor | 103 默认开启 | +| 小范围验证 Cordis / plugin 模型 | 未批准的大流量 smoke | + +## 安装(本机一次) + +Memind launch plan 与 smoke 脚本固定 pin: + +```bash +npm install -g @deepseek-ai/dsh@0.1.1-rc.2 +dsh --version # → 0.1.1-rc.2 +which dsh # Homebrew 全局 npm 通常在 /opt/homebrew/bin/dsh +``` + +未装本地二进制时,代码会回退 `npx -y @deepseek-ai/dsh`(首次也会下载包)。 + +## 开关(默认全关) + +```bash +MEMIND_TOOL_GATEWAY_DSH_ENABLED=0 +MEMIND_TOOL_GATEWAY_DSH_TASK_TYPES=repo_refactor,multi_file +MEMIND_DSH_BIN=dsh +# MEMIND_DSH_FORCE_NPX=1 # 强制 npx 路径 +``` + +见 `docs/goose-v149-canary.env.example`。 + +## 离线验证(零 LLM) + +```bash +node scripts/check-dsh-executor-local.mjs +# → DSH_EXECUTOR_CLI_OK + +MEMIND_TOOL_GATEWAY_DSH_ENABLED=1 node scripts/check-dsh-executor-local.mjs +# → DSH_EXECUTOR_OK(仅 dry-run launch plan,不 spawn) +``` + +## Live smoke(耗 token,须批准) + +**默认禁止** Agent 自动跑 live。人工一次性: + +```bash +export DEEPSEEK_API_KEY=... +MEMIND_TOOL_GATEWAY_DSH_ENABLED=1 \ +MEMIND_DSH_SMOKE_TASK='List three files in the workspace root and stop.' \ +MEMIND_DSH_RUN_LIVE_SMOKE=1 \ +node scripts/check-dsh-executor-local.mjs +``` + +### Token 成本闸门(2026-09-10) + +- 优先 **dry-run / 单条最小 headless** +- **预计超过 10k token 必须先经用户同意** +- 禁止循环重跑、禁止 unattended `check-goosed-v149-all` / phase3 聚合 + +## headless 手动探针 + +```bash +dsh --profile headless "List three files in this directory and stop." +``` + +## 接入节奏(fusion-plan) + +| 阶段 | 内容 | +|------|------| +| **现在** | CLI + dry-run + 文档(本页) | +| **下一步** | Context Runtime shadow 自测后,**1 次**批准 live smoke | +| **之后** | task type 白名单扩展、fallback、memind_adm 开关;103 单独决策 | + +## 相关模块 + +- `dsh-agent-launch.mjs` — launch plan +- `tool-gateway.mjs` — executor 路由 +- `llm-providers.mjs` — provider 侧 plan diff --git a/scripts/check-dsh-executor-local.mjs b/scripts/check-dsh-executor-local.mjs index d27f3bc..feace24 100644 --- a/scripts/check-dsh-executor-local.mjs +++ b/scripts/check-dsh-executor-local.mjs @@ -1,11 +1,53 @@ #!/usr/bin/env node /** - * Local DeepSeek Harness executor dry-run probe. + * Local DeepSeek Harness (dsh) smoke — dev machine only, no LLM by default. + * + * Always verifies the CLI. When MEMIND_TOOL_GATEWAY_DSH_ENABLED=1, also prints + * a dry-run launch plan (does not spawn dsh). + * + * Live headless smoke (costs LLM tokens) is opt-in only: + * MEMIND_DSH_RUN_LIVE_SMOKE=1 node scripts/check-dsh-executor-local.mjs + * Any run expected to exceed 10k tokens requires explicit user approval. */ -import { buildDshExecutorLaunchPlan, dshExecutorEnabled } from '../dsh-agent-launch.mjs'; +import { spawnSync } from 'node:child_process'; +import { buildDshExecutorLaunchPlan, dshExecutorEnabled, resolveDshCommand } from '../dsh-agent-launch.mjs'; + +const cliName = process.env.MEMIND_DSH_BIN ?? process.env.DSH_BIN ?? 'dsh'; +const pinnedVersion = process.env.MEMIND_DSH_PINNED_VERSION ?? '0.1.1-rc.2'; + +function run(cmd, args, options = {}) { + return spawnSync(cmd, args, { encoding: 'utf8', ...options }); +} + +const versionResult = run(cliName, ['--version']); +const version = (versionResult.stdout ?? versionResult.stderr ?? '').trim(); +if (versionResult.status !== 0 || !version) { + console.error('DSH_EXECUTOR_FAIL: CLI not found'); + console.error(` install: npm install -g @deepseek-ai/dsh@${pinnedVersion}`); + process.exit(1); +} + +const resolved = resolveDshCommand(process.env); +console.log('DSH_EXECUTOR_PROBE:'); +console.log(` version=${version}`); +console.log(` command=${resolved.command}`); +console.log(` via_npx=${resolved.viaNpx}`); + +if (version !== pinnedVersion) { + console.warn(`DSH_EXECUTOR_WARN: expected ${pinnedVersion}, got ${version}`); +} + +const headlessHelp = run(resolved.command, resolved.viaNpx + ? ['-y', '@deepseek-ai/dsh', '--profile', 'headless', '--help'] + : ['--profile', 'headless', '--help']); +if (headlessHelp.status !== 0) { + console.error('DSH_EXECUTOR_FAIL: headless profile unavailable'); + process.exit(1); +} if (!dshExecutorEnabled(process.env)) { - console.log('DSH_EXECUTOR_SKIP: MEMIND_TOOL_GATEWAY_DSH_ENABLED is off'); + console.log('DSH_EXECUTOR_SKIP: MEMIND_TOOL_GATEWAY_DSH_ENABLED is off (launch plan not checked)'); + console.log('DSH_EXECUTOR_CLI_OK'); process.exit(0); } @@ -23,7 +65,7 @@ console.log(JSON.stringify({ ok: plan.ok, executor: plan.executor, command: plan.command, - argsPreview: plan.args?.slice?.(0, 4), + argsPreview: plan.args?.slice?.(0, 6), cwd: plan.cwd, message: plan.message ?? null, }, null, 2)); @@ -33,4 +75,40 @@ if (!plan.ok) { process.exit(1); } -console.log('DSH_EXECUTOR_OK: dry-run plan ready (use tool-gateway dry-run to execute)'); +if (process.env.MEMIND_DSH_RUN_LIVE_SMOKE !== '1') { + console.log('DSH_EXECUTOR_OK: dry-run plan ready (no LLM; set MEMIND_DSH_RUN_LIVE_SMOKE=1 to execute once)'); + process.exit(0); +} + +if (!process.env.DEEPSEEK_API_KEY && !process.env.OPENAI_API_KEY) { + console.error('DSH_EXECUTOR_FAIL: live smoke requires DEEPSEEK_API_KEY or OPENAI_API_KEY'); + process.exit(1); +} + +console.warn('DSH_EXECUTOR_LIVE: spawning one headless task (LLM tokens will be consumed)'); +const live = spawnSync(plan.command, plan.args, { + cwd: plan.cwd, + env: { ...process.env, ...plan.env }, + encoding: 'utf8', + maxBuffer: 1024 * 1024, + timeout: positiveTimeout(process.env.MEMIND_DSH_LIVE_TIMEOUT_MS, 120_000), +}); + +if (live.error) { + console.error(`DSH_EXECUTOR_FAIL: ${live.error.message}`); + process.exit(1); +} +if (live.status !== 0) { + console.error(`DSH_EXECUTOR_FAIL: exit ${live.status}`); + if (live.stderr) console.error(live.stderr.slice(0, 2000)); + process.exit(1); +} + +const preview = (live.stdout ?? '').trim().slice(0, 500); +console.log(`DSH_EXECUTOR_LIVE_OK: stdout_preview=${JSON.stringify(preview)}`); + +function positiveTimeout(value, fallback) { + const n = Number(value); + if (!Number.isFinite(n) || n <= 0) return fallback; + return Math.floor(n); +}