Skip to content

Commit 953338f

Browse files
王璨claude
andcommitted
refactor: separate command config from settings files
Store /config-managed values only in ~/.dscode/config.json while moving declarative user and project settings to settings.json. This removes pre-1.0 compatibility baggage and aligns the runtime, tests, and docs around the new config model. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ef5f667 commit 953338f

10 files changed

Lines changed: 241 additions & 67 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ new Agent({
6666

6767
## 数据位置
6868

69-
- 配置: `~/.dscode/config.json` (用户级) + `<project>/.dscode/config.json` (项目级)
69+
- 命令配置: `~/.dscode/config.json``/config` 写入)
70+
- 声明式 settings: `~/.dscode/settings.json`(用户级) + `<project>/.dscode/settings.json`(项目级)
7071
- 数据: `~/.dscode/data/`
7172
- `sessions/` — 会话历史
7273
- `memory/` — 全局 + 项目记忆

README.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ node dist/dscode.mjs
8383
/config model deepseek-v4-pro
8484
```
8585

86-
配置会持久化到 `~/.dscode/config.json`,后续启动无需重复设置。
86+
`/config` 命令写入的配置会持久化到 `~/.dscode/config.json`,后续启动无需重复设置。
87+
声明式配置单独放在 `settings.json`:用户级 `~/.dscode/settings.json`,项目级 `<project>/.dscode/settings.json`
8788

8889
## What you get
8990

@@ -163,6 +164,8 @@ DeepSeek reasoning 模型通过 `thinkingLevel` 控制思考强度,切换模
163164

164165
### MCP 配置示例
165166

167+
将 MCP 写在 `~/.dscode/settings.json``<project>/.dscode/settings.json` 中,而不是 `/config` 写入的 `config.json`
168+
166169
```jsonc
167170
{
168171
"mcpServers": {
@@ -233,7 +236,7 @@ Always push the branch before creating a PR.
233236
| 命令 | 说明 |
234237
| --- | --- |
235238
| `/help` | 显示所有命令 |
236-
| `/config` | 查看或修改配置(模型 / Key / 思考) |
239+
| `/config` | 查看或修改用户命令配置(模型 / Key / 思考 / cwd|
237240
| `/reset` | 清空对话历史 |
238241
| `/session list/save/load/delete` | 会话管理 |
239242
| `/memory list/add/remove/clear` | 记忆管理 |
@@ -247,20 +250,38 @@ Always push the branch before creating a PR.
247250

248251
## Configuration reference
249252

250-
推荐使用 `/config` 在 TUI 中管理配置。配置持久化到 JSON 文件,项目级覆盖用户级:
253+
推荐把配置分成两类:
254+
255+
- `/config` 命令写入的用户命令配置:`~/.dscode/config.json`
256+
- 声明式 settings:
257+
- 用户级:`~/.dscode/settings.json`
258+
- 项目级:`<project>/.dscode/settings.json`
251259

252-
- 用户级:`~/.dscode/config.json`
253-
- 项目级:`<project>/.dscode/config.json`
260+
项目级配置统一使用 `<project>/.dscode/settings.json`
261+
262+
### `/config` command config
254263

255264
```jsonc
256265
{
257-
"provider": "deepseek",
258266
"modelId": "deepseek-v4-flash",
259-
"maxTokens": 16384,
260267
"thinkingLevel": "high",
268+
"cwd": "/absolute/path/to/project"
269+
}
270+
```
271+
272+
### `settings.json`
273+
274+
```jsonc
275+
{
261276
"skills": ["git-workflow"],
262277
"permissions": {
263278
"deny": ["**/.env", "**/.env.*", "**/secrets/**"]
279+
},
280+
"mcpServers": {
281+
"playwright": {
282+
"command": "npx",
283+
"args": ["@anthropic/mcp-playwright"]
284+
}
264285
}
265286
}
266287
```

docs/architecture-overview.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ src/
7272
├── core/
7373
│ ├── main.ts # 入口
7474
│ ├── harness.ts # host / composition root
75-
│ ├── config.ts # 配置加载 (env + ~/.dscode + project .dscode)
75+
│ ├── config.ts # 配置加载 (env + config.json + settings.json)
7676
│ └── types.ts # 跨模块共享类型
7777
7878
├── session/
@@ -114,7 +114,8 @@ src/
114114

115115
```
116116
~/.dscode/
117-
├── config.json # 用户配置 (模型, 权限规则)
117+
├── config.json # `/config` 写入的用户命令配置(model / thinking / cwd / apiKey)
118+
├── settings.json # 用户级声明式 settings(permissions / mcp / skills)
118119
└── data/
119120
├── sessions/
120121
│ ├── <ulid>.json # 单个 session 完整数据
@@ -129,7 +130,7 @@ src/
129130

130131
```
131132
<project>/.dscode/
132-
└── config.json # 项目级配置(覆盖用户级)
133+
└── settings.json # 项目级声明式 settings(覆盖用户级 settings
133134
```
134135

135136
环境变量:

docs/harness.md

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -218,29 +218,26 @@ main();
218218
```typescript
219219
// src/config.ts
220220
function loadConfig(): HarnessConfig {
221-
// 1. 确定项目路径
222-
const projectPath = resolve(process.env.DSCODE_PROJECT_PATH ?? process.cwd());
221+
const startupPath = resolve(process.env.DSCODE_PROJECT_PATH ?? process.cwd());
222+
const userCommandConfig = loadJsonSafe(join(dsConfigHome(), "config.json"));
223+
const userSettings = loadJsonSafe(join(dsConfigHome(), "settings.json"));
223224

224-
// 2. 加载 .env (项目目录)
225-
loadEnvFile(projectPath);
225+
const projectPath = existsSync(userCommandConfig.cwd)
226+
? resolve(userCommandConfig.cwd)
227+
: startupPath;
226228

227-
// 3. 加载两级配置
228-
const userConfig = loadJsonSafe(join(dsConfigHome(), "config.json"));
229-
const projectConfig = loadJsonSafe(join(projectPath, ".dscode", "config.json"));
230-
const merged = { ...userConfig, ...projectConfig };
229+
const projectSettings = loadJsonSafe(join(projectPath, ".dscode", "settings.json"));
231230

232-
// 4. 环境变量覆盖
233-
const provider = process.env.AGENT_PROVIDER ?? merged.provider ?? "deepseek";
234-
const modelId = process.env.AGENT_MODEL ?? process.env.DEEPSEEK_MODEL ?? merged.modelId ?? "deepseek-v4-flash";
235-
const maxTokens = Number(process.env.DSCODE_MAX_TOKENS) || merged.maxTokens || 16384;
236-
237-
// 5. permissions.deny 两级取并集
238-
const denyPatterns = [...new Set([...userDeny, ...projectDeny])];
231+
const merged = { ...userSettings, ...projectSettings };
239232

240-
// 合并优先级: 默认值 < 用户级 < 项目级 < 环境变量
233+
const provider = process.env.AGENT_PROVIDER ?? merged.provider ?? "deepseek";
234+
const modelId = process.env.AGENT_MODEL ?? process.env.DEEPSEEK_MODEL ?? userCommandConfig.modelId ?? "deepseek-v4-flash";
241235
}
242236
```
243237

238+
`/config` 命令写入的字段(如 `modelId` / `thinkingLevel` / `cwd` / `apiKey`)来自用户级 `~/.dscode/config.json`
239+
`permissions` / `mcp` / `skills` 等声明式配置来自 `settings.json`,其中项目级 `settings.json` 覆盖用户级 `settings.json`
240+
244241
## 模型切换
245242

246243
```typescript

docs/integration-report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Submitted DSCode as a new integration guide to [awesome-deepseek-agent](https://
2020
Both guides cover:
2121
- Prerequisites (Node.js 20.6+, DeepSeek API Key)
2222
- Installation (git clone + npm install)
23-
- Configuration (`.env`, two-level `config.json`)
23+
- Configuration (`.env`, user `config.json`, and user/project `settings.json`)
2424
- Model and thinking mode setup (`modelId`, `thinkingLevel`, `maxTokens`)
2525
- 1M context window note
2626
- Running and slash commands reference

docs/layer4-skills.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ Agent
275275

276276
### 配置方式
277277

278-
`~/.dscode/config.json``<project>/.dscode/config.json` 中配置:
278+
`~/.dscode/settings.json``<project>/.dscode/settings.json` 中配置:
279279

280280
```json
281281
{

src/core/config.ts

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ function dsDataHome(): string {
1414
return process.env.DSCODE_DATA_HOME ?? join(homedir(), ".dscode");
1515
}
1616

17+
function userConfigPath(): string {
18+
return join(dsConfigHome(), "config.json");
19+
}
20+
21+
function userSettingsPath(): string {
22+
return join(dsConfigHome(), "settings.json");
23+
}
24+
25+
function projectSettingsPath(projectPath: string): string {
26+
return join(projectPath, ".dscode", "settings.json");
27+
}
28+
1729
function loadJsonSafe(path: string): Record<string, unknown> {
1830
if (!existsSync(path)) return {};
1931
try {
@@ -34,52 +46,65 @@ export function maskApiKey(key: string | undefined): string {
3446
return key.slice(0, 3) + "****" + key.slice(-4);
3547
}
3648

37-
export function saveUserConfig(partial: Record<string, unknown>): void {
38-
const path = join(dsConfigHome(), "config.json");
39-
const existing = loadJsonSafe(path);
40-
saveJsonSafe(path, { ...existing, ...partial });
49+
function loadUserCommandConfig(): Record<string, unknown> {
50+
return loadJsonSafe(userConfigPath());
4151
}
4252

43-
export function saveProjectConfig(partial: Record<string, unknown>, projectPath: string): void {
44-
const path = join(projectPath, ".dscode", "config.json");
45-
const existing = loadJsonSafe(path);
53+
function loadScopedSettings(settingsPath: string): Record<string, unknown> {
54+
return loadJsonSafe(settingsPath);
55+
}
56+
57+
export function saveUserConfig(partial: Record<string, unknown>): void {
58+
const path = userConfigPath();
59+
const existing = loadUserCommandConfig();
4660
saveJsonSafe(path, { ...existing, ...partial });
4761
}
4862

4963
export function loadConfig(): HarnessConfig {
50-
const projectPath = resolve(process.env.DSCODE_PROJECT_PATH ?? process.cwd());
64+
const startupPath = resolve(process.env.DSCODE_PROJECT_PATH ?? process.cwd());
65+
const configDir = dsConfigHome();
66+
const dataDir = join(dsDataHome(), "data");
67+
68+
const userConfig = loadUserCommandConfig();
69+
const userSettings = loadScopedSettings(userSettingsPath());
70+
71+
let projectPath = startupPath;
72+
const configuredCwd = userConfig.cwd;
73+
if (typeof configuredCwd === "string" && configuredCwd.trim() !== "") {
74+
const nextProjectPath = resolve(configuredCwd);
75+
if (existsSync(nextProjectPath)) {
76+
projectPath = nextProjectPath;
77+
}
78+
}
79+
5180
if (projectPath !== process.cwd()) {
5281
process.chdir(projectPath);
5382
}
5483

55-
const configDir = dsConfigHome();
56-
const dataDir = join(dsDataHome(), "data");
57-
58-
const userConfig = loadJsonSafe(join(configDir, "config.json"));
59-
const projectConfig = loadJsonSafe(join(projectPath, ".dscode", "config.json"));
60-
const merged = { ...userConfig, ...projectConfig };
84+
const projectSettings = loadScopedSettings(projectSettingsPath(projectPath));
85+
const merged = { ...userSettings, ...projectSettings };
6186

6287
const provider = (process.env.AGENT_PROVIDER as string) ?? (merged.provider as string) ?? "deepseek";
63-
const modelId = (process.env.AGENT_MODEL as string) ?? (process.env.DEEPSEEK_MODEL as string) ?? (merged.modelId as string) ?? "deepseek-v4-flash";
88+
const modelId = (process.env.AGENT_MODEL as string) ?? (process.env.DEEPSEEK_MODEL as string) ?? (userConfig.modelId as string) ?? (merged.modelId as string) ?? "deepseek-v4-flash";
6489
const maxTokens = Number(process.env.DSCODE_MAX_TOKENS) || (merged.maxTokens as number) || 16384;
6590

6691
// API key: env var > user config (never project config for security)
6792
const apiKey = process.env.DEEPSEEK_API_KEY ?? (userConfig.apiKey as string | undefined);
6893

69-
const userDeny = ((userConfig.permissions as any)?.deny as string[]) ?? [];
70-
const projectDeny = ((projectConfig.permissions as any)?.deny as string[]) ?? [];
94+
const userDeny = ((userSettings.permissions as any)?.deny as string[]) ?? [];
95+
const projectDeny = ((projectSettings.permissions as any)?.deny as string[]) ?? [];
7196
const denyPatterns = [...new Set([...userDeny, ...projectDeny])];
7297

73-
const userSkills = ((userConfig.skills as string[]) ?? []);
74-
const projectSkills = ((projectConfig.skills as string[]) ?? []);
98+
const userSkills = ((userSettings.skills as string[]) ?? []);
99+
const projectSkills = ((projectSettings.skills as string[]) ?? []);
75100
const skills = [...new Set([...userSkills, ...projectSkills])];
76101

77102
const userSkillsDir = join(configDir, "skills");
78103
const projectSkillsDir = join(projectPath, ".dscode", "skills");
79104

80105
const validThinkingLevels = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
81106
const defaultThinkingLevel: ThinkingLevel = modelId.includes("pro") ? "medium" : "off";
82-
const rawThinkingLevel = process.env.AGENT_THINKING_LEVEL ?? merged.thinkingLevel;
107+
const rawThinkingLevel = process.env.AGENT_THINKING_LEVEL ?? userConfig.thinkingLevel ?? merged.thinkingLevel;
83108
const thinkingLevel: ThinkingLevel = rawThinkingLevel !== undefined && validThinkingLevels.has(rawThinkingLevel as string)
84109
? (rawThinkingLevel as ThinkingLevel)
85110
: defaultThinkingLevel;

src/core/harness.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getModel, streamSimple, Type } from "@mariozechner/pi-ai";
44
import type { Api, AssistantMessage, Context, Model, SimpleStreamOptions } from "@mariozechner/pi-ai";
55

66
import type { HarnessConfig } from "./types.js";
7-
import { saveProjectConfig } from "./config.js";
7+
import { saveUserConfig } from "./config.js";
88
import { SessionManager } from "../session/manager.js";
99
import { ContextManager } from "../context/manager.js";
1010
import { MemoryManager } from "../memory/manager.js";
@@ -189,13 +189,13 @@ export class Harness {
189189
this.config.thinkingLevel = thinkingLevel;
190190
this.agent.state.thinkingLevel = thinkingLevel;
191191

192-
saveProjectConfig({ modelId, thinkingLevel }, this.config.projectPath);
192+
saveUserConfig({ modelId, thinkingLevel });
193193
}
194194

195195
setThinking(level: string): void {
196196
this.config.thinkingLevel = level as any;
197197
this.agent.state.thinkingLevel = level as any;
198-
saveProjectConfig({ thinkingLevel: level }, this.config.projectPath);
198+
saveUserConfig({ thinkingLevel: level });
199199
}
200200

201201
private async shutdown(): Promise<void> {

src/ui/commands.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { PermissionManager } from "../permissions/manager.js";
1313
import type { ContextManager } from "../context/manager.js";
1414
import type { MCPManager } from "../mcp/manager.js";
1515
import type { TuiApp } from "./tui-app.js";
16-
import { saveUserConfig, saveProjectConfig, maskApiKey } from "../core/config.js";
16+
import { saveUserConfig, maskApiKey } from "../core/config.js";
1717
import { readImageFile, readClipboardImage } from "../utils/image.js";
1818

1919
interface CommandContext {
@@ -243,17 +243,17 @@ const COMMANDS: SlashCommandDef[] = [
243243
},
244244
{
245245
name: "config",
246-
description: "Show or change configuration (model|cwd|key)",
246+
description: "Show or change user command config",
247247
execute: async (args, ctx) => {
248248
const [sub, ...rest] = args.split(/\s+/);
249249
switch (sub) {
250250
case "help": {
251251
ctx.tui.addInfo([
252-
"/config Show current settings",
253-
"/config model <id> Switch model (e.g., deepseek-v4-pro, deepseek-chat)",
254-
"/config thinking <level> Set thinking level (off|minimal|low|medium|high|xhigh)",
252+
"/config Show current user command config",
253+
"/config model <id> Switch model (saved to ~/.dscode/config.json)",
254+
"/config thinking <level> Set thinking level (saved to ~/.dscode/config.json)",
255255
"/config key <api-key> Set your DeepSeek API key",
256-
"/config cwd <path> Set working directory (restart to apply)",
256+
"/config cwd <path> Set working directory (saved to ~/.dscode/config.json)",
257257
"/config help Show this help",
258258
].join("\n"));
259259
break;
@@ -275,8 +275,8 @@ const COMMANDS: SlashCommandDef[] = [
275275
return;
276276
}
277277
const resolvedCwd = resolve(cwd);
278-
saveProjectConfig({ cwd: resolvedCwd }, ctx.config.projectPath);
279-
ctx.tui.addInfo(`CWD set to: ${resolvedCwd} (restart required to take effect)`);
278+
saveUserConfig({ cwd: resolvedCwd });
279+
ctx.tui.addInfo(`CWD set to: ${resolvedCwd} (saved to ~/.dscode/config.json, restart required)`);
280280
break;
281281
}
282282
case "key": {
@@ -287,7 +287,7 @@ const COMMANDS: SlashCommandDef[] = [
287287
}
288288
saveUserConfig({ apiKey: key });
289289
process.env.DEEPSEEK_API_KEY = key;
290-
ctx.tui.addInfo(`API key saved: ${maskApiKey(key)}`);
290+
ctx.tui.addInfo(`API key saved to ~/.dscode/config.json: ${maskApiKey(key)}`);
291291
break;
292292
}
293293
case "thinking": {
@@ -303,12 +303,16 @@ const COMMANDS: SlashCommandDef[] = [
303303
}
304304
default: {
305305
const lines = [
306-
` provider ${ctx.config.provider}`,
307-
` model ${ctx.config.modelId}`,
308-
` apiKey ${maskApiKey(ctx.config.apiKey)}`,
309-
` cwd ${ctx.config.projectPath}`,
310-
` maxTokens ${ctx.config.maxTokens}`,
311-
` thinking ${ctx.config.thinkingLevel}`,
306+
` provider ${ctx.config.provider}`,
307+
` model ${ctx.config.modelId}`,
308+
` apiKey ${maskApiKey(ctx.config.apiKey)}`,
309+
` cwd ${ctx.config.projectPath}`,
310+
` maxTokens ${ctx.config.maxTokens}`,
311+
` thinking ${ctx.config.thinkingLevel}`,
312+
"",
313+
" command file ~/.dscode/config.json",
314+
" settings ~/.dscode/settings.json",
315+
" project <project>/.dscode/settings.json",
312316
"",
313317
"Type /config help for usage.",
314318
];

0 commit comments

Comments
 (0)