feat(dev-tools): document dsh install and zero-LLM local checks
Memind CI / Test, build, and release guards (push) Failing after 3m44s
Memind CI / Test, build, and release guards (push) Failing after 3m44s
Pin @deepseek-ai/dsh@0.1.1-rc.2 probing in check-dsh-executor-local.mjs, keep live headless smoke opt-in only, and record the 10k-token approval gate. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user