codemini-cli 0.5.8 → 0.5.10
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/README.md +354 -225
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-CCcxtQK_.js → highlighted-body-OFNGDK62-7HL7yft8.js} +1 -1
- package/codemini-web/dist/assets/{index-Cy4HN-FS.js → index-BK75hMb2.js} +95 -93
- package/codemini-web/dist/assets/index-BSdIdn3L.css +2 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +1 -0
- package/codemini-web/dist/index.html +2 -2
- package/codemini-web/lib/runtime-bridge.js +547 -546
- package/codemini-web/server.js +318 -233
- package/package.json +67 -67
- package/skills/brainstorm/SKILL.md +8 -3
- package/skills/codemini.skills.json +40 -0
- package/src/commands/skill.js +16 -5
- package/src/core/ast.js +30 -15
- package/src/core/chat-runtime.js +88 -16
- package/src/core/command-loader.js +120 -25
- package/src/core/command-policy.js +34 -10
- package/src/core/config-store.js +14 -1
- package/src/core/project-index.js +21 -2
- package/src/core/project-instructions.js +98 -0
- package/src/core/shell.js +79 -73
- package/src/core/system-prompt-composer.js +10 -0
- package/src/core/tools.js +114 -65
- package/codemini-web/dist/assets/index-CMISAOFr.css +0 -2
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-BWFzYc7A.js +0 -1
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codemini-cli",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"cli",
|
|
7
|
-
"ai",
|
|
8
|
-
"coding-assistant",
|
|
9
|
-
"developer-tools",
|
|
10
|
-
"terminal",
|
|
11
|
-
"windows",
|
|
12
|
-
"powershell"
|
|
13
|
-
],
|
|
14
|
-
"type": "module",
|
|
15
|
-
"homepage": "https://github.com/havingautism/Codemini-CLI#readme",
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/havingautism/Codemini-CLI/issues"
|
|
18
|
-
},
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/havingautism/Codemini-CLI.git"
|
|
22
|
-
},
|
|
23
|
-
"bin": {
|
|
24
|
-
"codemini": "bin/coder.js",
|
|
25
|
-
"coder": "bin/coder.js"
|
|
26
|
-
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"start": "node bin/coder.js",
|
|
29
|
-
"test": "node --test tests/*.test.js",
|
|
30
|
-
"build:web": "npm install --prefix codemini-web && npm run build --prefix codemini-web",
|
|
31
|
-
"prepack": "npm run build:web",
|
|
32
|
-
"pack:offline": "npm pack",
|
|
33
|
-
"bump:patch": "npm version patch --no-git-tag-version",
|
|
34
|
-
"bump:minor": "npm version minor --no-git-tag-version",
|
|
35
|
-
"bump:major": "npm version major --no-git-tag-version"
|
|
36
|
-
},
|
|
37
|
-
"files": [
|
|
38
|
-
"bin",
|
|
39
|
-
"src",
|
|
40
|
-
"codemini-web/server.js",
|
|
41
|
-
"codemini-web/lib",
|
|
42
|
-
"codemini-web/dist",
|
|
43
|
-
"codemini-web/codemini_logo.png",
|
|
44
|
-
"souls",
|
|
45
|
-
"templates",
|
|
46
|
-
"skills",
|
|
47
|
-
"README.md",
|
|
48
|
-
"OPERATIONS.md",
|
|
49
|
-
"deployment.md"
|
|
50
|
-
],
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=22"
|
|
53
|
-
},
|
|
54
|
-
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
},
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"@cursorless/tree-sitter-wasms": "^0.8.1",
|
|
59
|
-
"cheerio": "^1.1.2",
|
|
60
|
-
"cli-truncate": "^6.0.0",
|
|
61
|
-
"ink": "^7.0.0",
|
|
62
|
-
"react": "^19.2.5",
|
|
63
|
-
"strip-ansi": "^7.2.0",
|
|
64
|
-
"web-tree-sitter": "^0.26.8"
|
|
65
|
-
},
|
|
66
|
-
"license": "MIT"
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "codemini-cli",
|
|
3
|
+
"version": "0.5.10",
|
|
4
|
+
"description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"ai",
|
|
8
|
+
"coding-assistant",
|
|
9
|
+
"developer-tools",
|
|
10
|
+
"terminal",
|
|
11
|
+
"windows",
|
|
12
|
+
"powershell"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"homepage": "https://github.com/havingautism/Codemini-CLI#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/havingautism/Codemini-CLI/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/havingautism/Codemini-CLI.git"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"codemini": "bin/coder.js",
|
|
25
|
+
"coder": "bin/coder.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "node bin/coder.js",
|
|
29
|
+
"test": "node --test tests/*.test.js",
|
|
30
|
+
"build:web": "npm install --prefix codemini-web && npm run build --prefix codemini-web",
|
|
31
|
+
"prepack": "npm run build:web",
|
|
32
|
+
"pack:offline": "npm pack",
|
|
33
|
+
"bump:patch": "npm version patch --no-git-tag-version",
|
|
34
|
+
"bump:minor": "npm version minor --no-git-tag-version",
|
|
35
|
+
"bump:major": "npm version major --no-git-tag-version"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"bin",
|
|
39
|
+
"src",
|
|
40
|
+
"codemini-web/server.js",
|
|
41
|
+
"codemini-web/lib",
|
|
42
|
+
"codemini-web/dist",
|
|
43
|
+
"codemini-web/codemini_logo.png",
|
|
44
|
+
"souls",
|
|
45
|
+
"templates",
|
|
46
|
+
"skills",
|
|
47
|
+
"README.md",
|
|
48
|
+
"OPERATIONS.md",
|
|
49
|
+
"deployment.md"
|
|
50
|
+
],
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=22"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@cursorless/tree-sitter-wasms": "^0.8.1",
|
|
59
|
+
"cheerio": "^1.1.2",
|
|
60
|
+
"cli-truncate": "^6.0.0",
|
|
61
|
+
"ink": "^7.0.0",
|
|
62
|
+
"react": "^19.2.5",
|
|
63
|
+
"strip-ansi": "^7.2.0",
|
|
64
|
+
"web-tree-sitter": "^0.26.8"
|
|
65
|
+
},
|
|
66
|
+
"license": "MIT"
|
|
67
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brainstorm
|
|
3
3
|
description: Lightweight brainstorming skill. Use when a feature or behavior request has multiple reasonable approaches and the missing piece is user preference, tradeoff choice, or key constraint.
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Use this skill only when the task needs clarification or option comparison before coding.
|
|
@@ -18,6 +18,7 @@ Do NOT skip this skill for tasks that appear straightforward but lack clear requ
|
|
|
18
18
|
2. **Give 2-3 short options** only when the blocking constraint is already clear. Keep options concrete and focused on the main tradeoff.
|
|
19
19
|
3. **Present conclusions as suggested decisions**, not final choices.
|
|
20
20
|
4. **Do NOT write code, pseudo-code, file edits, or broad repo exploration** while direction is still being chosen.
|
|
21
|
+
5. **Stop after your brainstorm response.** Do not say "I will start", "starting now", "I'll edit", or otherwise transition into implementation in the same turn.
|
|
21
22
|
|
|
22
23
|
## Output Formats
|
|
23
24
|
|
|
@@ -54,6 +55,8 @@ Suggested decision:
|
|
|
54
55
|
- reason: <why>
|
|
55
56
|
```
|
|
56
57
|
|
|
58
|
+
After Mode B, STOP. Wait for the user to approve, reject, or revise the suggested decision.
|
|
59
|
+
|
|
57
60
|
## Self-Review
|
|
58
61
|
|
|
59
62
|
Before presenting options or a suggested decision, quickly check:
|
|
@@ -64,9 +67,11 @@ Before presenting options or a suggested decision, quickly check:
|
|
|
64
67
|
|
|
65
68
|
## Exit
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
Brainstorm ends only when the user sends a later message that clearly approves a direction, for example "use option 2", "按这个做", "确认,开始实现", or "直接写代码".
|
|
71
|
+
|
|
72
|
+
After that later user approval:
|
|
68
73
|
|
|
69
74
|
- If the task is small and clear enough to implement directly → proceed to code.
|
|
70
75
|
- If the task is non-trivial or touches multiple areas → YOU MUST invoke `writing-plans` to create an implementation plan before coding.
|
|
71
76
|
|
|
72
|
-
Do NOT
|
|
77
|
+
Do NOT treat your own suggested decision as approval. Do NOT continue from the brainstorm conclusion into planning or implementation until the user has explicitly approved a direction in a separate message.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"skills": {
|
|
4
|
+
"superpowers-lite": {
|
|
5
|
+
"description": "Default concise workflow for coding tasks; keep context tight, choose the right route, and verify before claiming success.",
|
|
6
|
+
"mode": "always",
|
|
7
|
+
"triggers": [],
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"priority": 100
|
|
10
|
+
},
|
|
11
|
+
"brainstorm": {
|
|
12
|
+
"description": "Use when a feature or behavior request has multiple reasonable approaches and the missing piece is user preference, tradeoff choice, or key constraint.",
|
|
13
|
+
"mode": "agent_requested",
|
|
14
|
+
"triggers": [],
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"priority": 70
|
|
17
|
+
},
|
|
18
|
+
"writing-plans": {
|
|
19
|
+
"description": "Use when you have a clear goal or approved design for a non-trivial task, before touching code.",
|
|
20
|
+
"mode": "agent_requested",
|
|
21
|
+
"triggers": [],
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"priority": 75
|
|
24
|
+
},
|
|
25
|
+
"grill-me": {
|
|
26
|
+
"description": "Use when the user explicitly asks to pressure-test a plan, architecture choice, PR, launch, or product idea.",
|
|
27
|
+
"mode": "agent_requested",
|
|
28
|
+
"triggers": [],
|
|
29
|
+
"enabled": true,
|
|
30
|
+
"priority": 65
|
|
31
|
+
},
|
|
32
|
+
"project-requirements": {
|
|
33
|
+
"description": "Create a project requirements report with repository inspection and structured output artifacts.",
|
|
34
|
+
"mode": "manual",
|
|
35
|
+
"triggers": [],
|
|
36
|
+
"enabled": true,
|
|
37
|
+
"priority": 50
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/commands/skill.js
CHANGED
|
@@ -77,9 +77,15 @@ export async function listSkillEntries({ scope = 'all', cwd = process.cwd() } =
|
|
|
77
77
|
name: command.name,
|
|
78
78
|
version: command.metadata?.version || '0.0.0',
|
|
79
79
|
description: command.metadata?.description || '',
|
|
80
|
+
mode: command.metadata?.mode || '',
|
|
81
|
+
triggers: Array.isArray(command.metadata?.triggers) ? command.metadata.triggers : [],
|
|
80
82
|
scope: itemScope,
|
|
81
83
|
path: command.path,
|
|
82
|
-
enabled:
|
|
84
|
+
enabled: command.metadata?.enabled === false
|
|
85
|
+
? false
|
|
86
|
+
: itemScope === 'builtin'
|
|
87
|
+
? true
|
|
88
|
+
: config.skills?.enabled?.[command.name] !== false
|
|
83
89
|
});
|
|
84
90
|
}
|
|
85
91
|
return entries.sort((a, b) => `${a.scope}:${a.name}`.localeCompare(`${b.scope}:${b.name}`));
|
|
@@ -93,14 +99,19 @@ async function readSkillMeta(name, { scope = 'all', cwd = process.cwd() } = {})
|
|
|
93
99
|
}
|
|
94
100
|
const dir = path.dirname(found.path);
|
|
95
101
|
const manifestPath = path.join(dir, 'manifest.json');
|
|
102
|
+
const catalogPath = path.join(path.dirname(dir), 'codemini.skills.json');
|
|
96
103
|
let manifest = null;
|
|
97
104
|
try {
|
|
98
|
-
|
|
105
|
+
const catalog = JSON.parse(await fs.readFile(catalogPath, 'utf8'));
|
|
106
|
+
manifest = catalog?.skills?.[found.name] || null;
|
|
99
107
|
} catch {
|
|
100
|
-
|
|
108
|
+
try {
|
|
109
|
+
manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
110
|
+
} catch {
|
|
111
|
+
manifest = null;
|
|
112
|
+
}
|
|
101
113
|
}
|
|
102
|
-
const
|
|
103
|
-
const skillPath = found.path || path.join(dir, entryFile);
|
|
114
|
+
const skillPath = found.path || path.join(dir, 'SKILL.md');
|
|
104
115
|
try {
|
|
105
116
|
const content = await fs.readFile(skillPath, 'utf8');
|
|
106
117
|
const firstLines = content.split('\n').slice(0, 20).join('\n');
|
package/src/core/ast.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createRequire } from 'node:module';
|
|
|
4
4
|
import { Parser, Language, Query } from 'web-tree-sitter';
|
|
5
5
|
import { LANGUAGE_ALIASES, EXTENSION_LANGUAGE_MAP } from './constants.js';
|
|
6
6
|
import { sha256Prefixed as sha256 } from './crypto-utils.js';
|
|
7
|
+
import { BoundedCache } from './bounded-cache.js';
|
|
7
8
|
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
9
10
|
|
|
@@ -43,7 +44,7 @@ const parserInitPromise = Parser.init({
|
|
|
43
44
|
return scriptName === 'web-tree-sitter.wasm' ? TREE_SITTER_WASM_PATH : scriptName;
|
|
44
45
|
}
|
|
45
46
|
});
|
|
46
|
-
const languageCache = new
|
|
47
|
+
const languageCache = new BoundedCache({ maxSize: 16, ttlMs: 60 * 60 * 1000 });
|
|
47
48
|
|
|
48
49
|
function clipText(text, maxLen = 220) {
|
|
49
50
|
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
@@ -113,15 +114,34 @@ async function loadLanguage(language) {
|
|
|
113
114
|
if (languageCache.has(language)) return languageCache.get(language);
|
|
114
115
|
const wasmPath = LANGUAGE_WASM_PATHS[language];
|
|
115
116
|
if (!wasmPath) throw new Error(`Unsupported Tree-sitter language: ${language}`);
|
|
116
|
-
const
|
|
117
|
-
languageCache.set(language,
|
|
118
|
-
|
|
117
|
+
const loadPromise = Language.load(wasmPath);
|
|
118
|
+
languageCache.set(language, loadPromise);
|
|
119
|
+
try {
|
|
120
|
+
return await loadPromise;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
languageCache.delete(language);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
async function
|
|
127
|
+
async function getParser(language) {
|
|
122
128
|
const loadedLanguage = await loadLanguage(language);
|
|
123
129
|
const parser = new Parser();
|
|
124
130
|
parser.setLanguage(loadedLanguage);
|
|
131
|
+
return { parser, loadedLanguage };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function deleteParsed(parsed) {
|
|
135
|
+
try {
|
|
136
|
+
parsed?.tree?.delete?.();
|
|
137
|
+
} catch {}
|
|
138
|
+
try {
|
|
139
|
+
parsed?.parser?.delete?.();
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function parseContent(content, language) {
|
|
144
|
+
const { parser, loadedLanguage } = await getParser(language);
|
|
125
145
|
const tree = parser.parse(content);
|
|
126
146
|
return { parser, tree, loadedLanguage };
|
|
127
147
|
}
|
|
@@ -188,8 +208,7 @@ export async function findEnclosingSymbol(content, filePath, line) {
|
|
|
188
208
|
} catch {
|
|
189
209
|
return null;
|
|
190
210
|
} finally {
|
|
191
|
-
|
|
192
|
-
if (parser) parser.delete();
|
|
211
|
+
deleteParsed({ tree, parser });
|
|
193
212
|
}
|
|
194
213
|
}
|
|
195
214
|
|
|
@@ -223,8 +242,7 @@ export async function queryAst(root, args) {
|
|
|
223
242
|
}
|
|
224
243
|
|
|
225
244
|
query.delete();
|
|
226
|
-
parsed
|
|
227
|
-
parsed.parser.delete();
|
|
245
|
+
deleteParsed(parsed);
|
|
228
246
|
|
|
229
247
|
return {
|
|
230
248
|
path: relativePath,
|
|
@@ -261,8 +279,7 @@ export async function readAstNode(root, args) {
|
|
|
261
279
|
child_summaries: node.namedChildren.slice(0, 8).map((child) => summarizeNode(child))
|
|
262
280
|
};
|
|
263
281
|
|
|
264
|
-
parsed
|
|
265
|
-
parsed.parser.delete();
|
|
282
|
+
deleteParsed(parsed);
|
|
266
283
|
return result;
|
|
267
284
|
}
|
|
268
285
|
|
|
@@ -277,15 +294,13 @@ export async function resolveAstTarget(root, relativePath, astTarget) {
|
|
|
277
294
|
const parsed = await parseFile(root, relativePath, astTarget.language);
|
|
278
295
|
const node = exactNodeForTarget(parsed.tree.rootNode, astTarget);
|
|
279
296
|
if (!node) {
|
|
280
|
-
parsed
|
|
281
|
-
parsed.parser.delete();
|
|
297
|
+
deleteParsed(parsed);
|
|
282
298
|
throw new Error('AST target no longer matches the current file');
|
|
283
299
|
}
|
|
284
300
|
|
|
285
301
|
const currentHash = sha256(node.text);
|
|
286
302
|
if (String(astTarget.range_hash || '') !== currentHash) {
|
|
287
|
-
parsed
|
|
288
|
-
parsed.parser.delete();
|
|
303
|
+
deleteParsed(parsed);
|
|
289
304
|
throw new Error('ast_target range_hash mismatch; the selected node changed and is now stale');
|
|
290
305
|
}
|
|
291
306
|
|
package/src/core/chat-runtime.js
CHANGED
|
@@ -128,6 +128,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
128
128
|
'context.prompt_budget_audit': 'Prompt 预算审计开关',
|
|
129
129
|
'context.microcompact_enabled': '微压缩(micro-compact)开关',
|
|
130
130
|
'context.microcompact_keep_recent': '微压缩保留最近工具结果数',
|
|
131
|
+
'context.project_instructions_enabled': '项目 AGENTS.md 注入开关',
|
|
132
|
+
'context.project_instructions_max_chars': '项目 AGENTS.md 字符上限',
|
|
131
133
|
'sessions.max_sessions': '会话保留上限',
|
|
132
134
|
'sessions.retention_days': '会话保留天数',
|
|
133
135
|
'shell.default': '默认 shell',
|
|
@@ -136,6 +138,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
136
138
|
'soul.preset': 'soul 预设',
|
|
137
139
|
'soul.custom_path': '自定义 soul 路径',
|
|
138
140
|
'policy.safe_mode': '安全模式开关',
|
|
141
|
+
'policy.allowed_paths': '安全模式目录白名单',
|
|
139
142
|
'policy.allow_dangerous_commands': '危险命令开关'
|
|
140
143
|
},
|
|
141
144
|
optionHints: {
|
|
@@ -145,8 +148,11 @@ function getCompletionCopy(language = 'zh') {
|
|
|
145
148
|
'execution.mode': '可选:auto | normal | plan',
|
|
146
149
|
'shell.default': '常用:bash | powershell',
|
|
147
150
|
'policy.safe_mode': '可选:true | false',
|
|
151
|
+
'policy.allowed_paths': 'JSON 数组,例如 ["D:\\\\shared"]',
|
|
148
152
|
'policy.allow_dangerous_commands': '可选:true | false',
|
|
149
|
-
'context.prompt_budget_audit': '可选:true | false'
|
|
153
|
+
'context.prompt_budget_audit': '可选:true | false',
|
|
154
|
+
'context.project_instructions_enabled': '可选:true | false',
|
|
155
|
+
'context.project_instructions_max_chars': '建议:8000-12000'
|
|
150
156
|
},
|
|
151
157
|
describeSet: (label, hint) => `设置${label}${hint ? `(${hint})` : ''}`,
|
|
152
158
|
describeGet: (label, hint) => `查看${label}${hint ? `(${hint})` : ''}`,
|
|
@@ -235,6 +241,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
235
241
|
'context.prompt_budget_audit': 'prompt budget audit switch',
|
|
236
242
|
'context.microcompact_enabled': 'micro-compact enabled',
|
|
237
243
|
'context.microcompact_keep_recent': 'micro-compact keep recent tool results',
|
|
244
|
+
'context.project_instructions_enabled': 'project AGENTS.md injection switch',
|
|
245
|
+
'context.project_instructions_max_chars': 'project AGENTS.md character limit',
|
|
238
246
|
'sessions.max_sessions': 'stored session limit',
|
|
239
247
|
'sessions.retention_days': 'session retention days',
|
|
240
248
|
'shell.default': 'default shell',
|
|
@@ -243,6 +251,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
243
251
|
'soul.preset': 'soul preset',
|
|
244
252
|
'soul.custom_path': 'custom soul prompt path',
|
|
245
253
|
'policy.safe_mode': 'safe mode switch',
|
|
254
|
+
'policy.allowed_paths': 'safe-mode allowed path roots',
|
|
246
255
|
'policy.allow_dangerous_commands': 'dangerous command allowance'
|
|
247
256
|
},
|
|
248
257
|
optionHints: {
|
|
@@ -252,8 +261,11 @@ function getCompletionCopy(language = 'zh') {
|
|
|
252
261
|
'execution.mode': 'options: auto | normal | plan',
|
|
253
262
|
'shell.default': 'common: bash | powershell',
|
|
254
263
|
'policy.safe_mode': 'options: true | false',
|
|
264
|
+
'policy.allowed_paths': 'JSON array, for example ["D:\\\\shared"]',
|
|
255
265
|
'policy.allow_dangerous_commands': 'options: true | false',
|
|
256
|
-
'context.prompt_budget_audit': 'options: true | false'
|
|
266
|
+
'context.prompt_budget_audit': 'options: true | false',
|
|
267
|
+
'context.project_instructions_enabled': 'options: true | false',
|
|
268
|
+
'context.project_instructions_max_chars': 'recommended: 8000-12000'
|
|
257
269
|
},
|
|
258
270
|
describeSet: (label, hint) => `set the ${label}${hint ? ` (${hint})` : ''}`,
|
|
259
271
|
describeGet: (label, hint) => `show the ${label}${hint ? ` (${hint})` : ''}`,
|
|
@@ -1265,6 +1277,7 @@ function isBundledSkillCommand(command) {
|
|
|
1265
1277
|
}
|
|
1266
1278
|
|
|
1267
1279
|
function isSkillEnabled(config, name, command = null) {
|
|
1280
|
+
if (command?.metadata?.enabled === false) return false;
|
|
1268
1281
|
if (isBundledSkillCommand(command)) return true;
|
|
1269
1282
|
return config.skills?.enabled?.[name] !== false;
|
|
1270
1283
|
}
|
|
@@ -2282,7 +2295,10 @@ function summarizePromptBudgetAudit(audit) {
|
|
|
2282
2295
|
}
|
|
2283
2296
|
|
|
2284
2297
|
function buildRuntimeStateSnapshot({ currentSession, config, model, executionMode, extraSession }) {
|
|
2285
|
-
const
|
|
2298
|
+
const activeParentMessages = Array.isArray(currentSession?.compact?.view) && currentSession.compact.view.length > 0
|
|
2299
|
+
? currentSession.compact.view
|
|
2300
|
+
: currentSession?.messages || [];
|
|
2301
|
+
const parentTokens = estimateMessagesTokens(activeParentMessages);
|
|
2286
2302
|
const subTokens = extraSession ? estimateMessagesTokens(extraSession.messages || []) : 0;
|
|
2287
2303
|
const currentContextTokens = parentTokens + subTokens;
|
|
2288
2304
|
const maxContextTokens = effectiveMaxContextTokens(config);
|
|
@@ -2301,6 +2317,9 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
2301
2317
|
maxContextTokens,
|
|
2302
2318
|
pendingPlanApproval: planState?.status === 'pending_approval'
|
|
2303
2319
|
? { goal: planState.goal, summary: planState.finalSummary || planState.summary, filePath: planState.filePath, steps: planState.steps || [] }
|
|
2320
|
+
: null,
|
|
2321
|
+
pendingReflectSkill: planState?.status === 'pending_reflect_skill'
|
|
2322
|
+
? buildPendingReflectSkillSnapshot(planState)
|
|
2304
2323
|
: null
|
|
2305
2324
|
};
|
|
2306
2325
|
Object.defineProperties(snapshot, {
|
|
@@ -2314,11 +2333,6 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
2314
2333
|
enumerable: false,
|
|
2315
2334
|
writable: false
|
|
2316
2335
|
},
|
|
2317
|
-
pendingReflectSkill: {
|
|
2318
|
-
value: currentSession?.planState?.status === 'pending_reflect_skill',
|
|
2319
|
-
enumerable: false,
|
|
2320
|
-
writable: false
|
|
2321
|
-
},
|
|
2322
2336
|
replyLanguage: {
|
|
2323
2337
|
value: getReplyLanguage(config),
|
|
2324
2338
|
enumerable: false,
|
|
@@ -2329,7 +2343,6 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
2329
2343
|
...snapshot,
|
|
2330
2344
|
currentContextTokens,
|
|
2331
2345
|
contextUsagePct,
|
|
2332
|
-
pendingReflectSkill: currentSession?.planState?.status === 'pending_reflect_skill',
|
|
2333
2346
|
replyLanguage: getReplyLanguage(config)
|
|
2334
2347
|
}),
|
|
2335
2348
|
enumerable: false,
|
|
@@ -2513,6 +2526,21 @@ function buildPendingReflectSkillMessage(reflectState) {
|
|
|
2513
2526
|
return lines.join('\n');
|
|
2514
2527
|
}
|
|
2515
2528
|
|
|
2529
|
+
function buildPendingReflectSkillSnapshot(reflectState) {
|
|
2530
|
+
const candidates = Array.isArray(reflectState?.candidates) ? reflectState.candidates : [];
|
|
2531
|
+
const candidate = candidates[0] || null;
|
|
2532
|
+
if (!candidate) return null;
|
|
2533
|
+
return {
|
|
2534
|
+
scope: reflectState?.targetScope || 'project',
|
|
2535
|
+
request: reflectState?.request || '',
|
|
2536
|
+
name: candidate.name || '',
|
|
2537
|
+
description: candidate.description || '',
|
|
2538
|
+
confidence: Number(candidate.confidence ?? 0.75),
|
|
2539
|
+
targetPath: candidate.targetPath || '',
|
|
2540
|
+
content: candidate.content || ''
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2516
2544
|
function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
|
|
2517
2545
|
const requirementPacket = buildGoalRequirementPacket(planState?.goal || '', 'coder');
|
|
2518
2546
|
const lines = [
|
|
@@ -2740,8 +2768,10 @@ async function askModel({
|
|
|
2740
2768
|
}
|
|
2741
2769
|
}
|
|
2742
2770
|
|
|
2771
|
+
const shouldGenerateTitle = text
|
|
2772
|
+
? !session.messages.some((msg) => msg?.role === 'user')
|
|
2773
|
+
: false;
|
|
2743
2774
|
if (text) {
|
|
2744
|
-
const shouldGenerateTitle = !session.messages.some((msg) => msg?.role === 'user');
|
|
2745
2775
|
const modelExtra =
|
|
2746
2776
|
typeof modelText === 'string' && modelText && modelText !== text ? { model_content: modelText } : {};
|
|
2747
2777
|
const userMessage = stampedMessage('user', text, modelExtra);
|
|
@@ -2773,7 +2803,16 @@ async function askModel({
|
|
|
2773
2803
|
|
|
2774
2804
|
const { definitions, handlers, formatters, deferredDefinitions, dispose: disposeTools } = getBuiltinTools({
|
|
2775
2805
|
workspaceRoot: process.cwd(),
|
|
2776
|
-
config
|
|
2806
|
+
config: {
|
|
2807
|
+
...config,
|
|
2808
|
+
policy: {
|
|
2809
|
+
...(config.policy || {}),
|
|
2810
|
+
allowed_paths: [
|
|
2811
|
+
...(Array.isArray(config.policy?.allowed_paths) ? config.policy.allowed_paths : []),
|
|
2812
|
+
path.join(getSessionsDir(), String(session.id))
|
|
2813
|
+
]
|
|
2814
|
+
}
|
|
2815
|
+
},
|
|
2777
2816
|
sessionId: session.id,
|
|
2778
2817
|
onSystemEvent: onAgentEvent,
|
|
2779
2818
|
getTodos: () => normalizeTodos(session.todos),
|
|
@@ -3004,7 +3043,7 @@ async function askModel({
|
|
|
3004
3043
|
await flushScheduledSave();
|
|
3005
3044
|
await saveSession(session);
|
|
3006
3045
|
// Generate a better title asynchronously after saving
|
|
3007
|
-
if (
|
|
3046
|
+
if (shouldGenerateTitle) {
|
|
3008
3047
|
const titleSessionId = session.id;
|
|
3009
3048
|
generateSessionTitle({
|
|
3010
3049
|
userText: text,
|
|
@@ -4242,11 +4281,22 @@ export async function createChatRuntime({
|
|
|
4242
4281
|
if (hasPendingPlanApproval(currentSession)) {
|
|
4243
4282
|
executionMode = 'plan';
|
|
4244
4283
|
}
|
|
4284
|
+
let compactState = null;
|
|
4285
|
+
const normalizeCompactThreshold = (value, fallback = 60) => {
|
|
4286
|
+
const num = Number(value);
|
|
4287
|
+
if (!Number.isFinite(num)) return fallback;
|
|
4288
|
+
return Math.min(95, Math.max(50, num));
|
|
4289
|
+
};
|
|
4290
|
+
const syncCompactStateFromConfig = () => {
|
|
4291
|
+
if (!compactState) return;
|
|
4292
|
+
compactState.threshold = normalizeCompactThreshold(config.context?.preflight_trigger_pct, 60);
|
|
4293
|
+
};
|
|
4245
4294
|
const syncRuntimeFromConfig = async ({ model: nextModel } = {}) => {
|
|
4246
4295
|
const configuredMode = String(config.execution?.mode || 'normal');
|
|
4247
4296
|
executionMode = hasPendingPlanApproval(currentSession)
|
|
4248
4297
|
? 'plan'
|
|
4249
4298
|
: (['normal', 'auto', 'plan'].includes(configuredMode) ? configuredMode : 'normal');
|
|
4299
|
+
syncCompactStateFromConfig();
|
|
4250
4300
|
|
|
4251
4301
|
const resolvedModel = String(nextModel || '').trim();
|
|
4252
4302
|
if (resolvedModel) {
|
|
@@ -4269,10 +4319,10 @@ export async function createChatRuntime({
|
|
|
4269
4319
|
// Set up tool result store under session directory
|
|
4270
4320
|
const sessionResultsDir = path.join(getSessionsDir(), String(currentSession.id));
|
|
4271
4321
|
setResultDir(sessionResultsDir);
|
|
4272
|
-
|
|
4322
|
+
compactState = {
|
|
4273
4323
|
backupMessages: null,
|
|
4274
4324
|
autoEnabled: true,
|
|
4275
|
-
threshold: 60,
|
|
4325
|
+
threshold: normalizeCompactThreshold(config.context?.preflight_trigger_pct, 60),
|
|
4276
4326
|
mode: 'conservative'
|
|
4277
4327
|
};
|
|
4278
4328
|
let compactedForModel = currentSession.compact?.view || null;
|
|
@@ -4349,6 +4399,8 @@ export async function createChatRuntime({
|
|
|
4349
4399
|
'context.read_file_max_chars',
|
|
4350
4400
|
'context.microcompact_enabled',
|
|
4351
4401
|
'context.microcompact_keep_recent',
|
|
4402
|
+
'context.project_instructions_enabled',
|
|
4403
|
+
'context.project_instructions_max_chars',
|
|
4352
4404
|
'sessions.max_sessions',
|
|
4353
4405
|
'sessions.retention_days',
|
|
4354
4406
|
'shell.timeout_ms',
|
|
@@ -4356,6 +4408,7 @@ export async function createChatRuntime({
|
|
|
4356
4408
|
'soul.preset',
|
|
4357
4409
|
'soul.custom_path',
|
|
4358
4410
|
'policy.safe_mode',
|
|
4411
|
+
'policy.allowed_paths',
|
|
4359
4412
|
'policy.allow_dangerous_commands'
|
|
4360
4413
|
];
|
|
4361
4414
|
|
|
@@ -4777,6 +4830,11 @@ export async function createChatRuntime({
|
|
|
4777
4830
|
};
|
|
4778
4831
|
|
|
4779
4832
|
const persistAssistantExchange = async (userText, assistantText, { includeUser = true, extra = {} } = {}) => {
|
|
4833
|
+
const priorUserCount = currentSession.messages.filter((msg) => msg?.role === 'user').length;
|
|
4834
|
+
const priorAssistantCount = currentSession.messages.filter((msg) => msg?.role === 'assistant').length;
|
|
4835
|
+
const shouldGenerateTitle =
|
|
4836
|
+
(includeUser && userText && priorUserCount === 0) ||
|
|
4837
|
+
(!includeUser && userText && priorUserCount === 1 && priorAssistantCount === 0);
|
|
4780
4838
|
if (includeUser && userText) {
|
|
4781
4839
|
appendSessionMessage(stampedMessage('user', userText));
|
|
4782
4840
|
}
|
|
@@ -4787,7 +4845,7 @@ export async function createChatRuntime({
|
|
|
4787
4845
|
currentSession.mode = executionMode || config.execution?.mode || 'normal';
|
|
4788
4846
|
await saveSession(currentSession);
|
|
4789
4847
|
// Generate a better title asynchronously after saving
|
|
4790
|
-
if (shouldReplaceSessionTitle(currentSession.title)) {
|
|
4848
|
+
if (shouldGenerateTitle || shouldReplaceSessionTitle(currentSession.title)) {
|
|
4791
4849
|
const titleSessionId = currentSession.id;
|
|
4792
4850
|
generateSessionTitle({
|
|
4793
4851
|
userText,
|
|
@@ -5115,6 +5173,7 @@ export async function createChatRuntime({
|
|
|
5115
5173
|
});
|
|
5116
5174
|
currentSession.planState = null;
|
|
5117
5175
|
executionMode = 'auto';
|
|
5176
|
+
if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
|
|
5118
5177
|
await reloadCommandsAndSkills();
|
|
5119
5178
|
const text = `Reflect skill written and loaded: /${written.draft.name}\nPath: ${written.filePath}`;
|
|
5120
5179
|
await persistLocalExchange(line, text, { includeUser: false });
|
|
@@ -5172,6 +5231,12 @@ export async function createChatRuntime({
|
|
|
5172
5231
|
workspaceRoot: process.cwd()
|
|
5173
5232
|
})
|
|
5174
5233
|
};
|
|
5234
|
+
if (onAgentEvent) {
|
|
5235
|
+
onAgentEvent({
|
|
5236
|
+
type: 'reflect:pending_approval',
|
|
5237
|
+
draft: buildPendingReflectSkillSnapshot(currentSession.planState)
|
|
5238
|
+
});
|
|
5239
|
+
}
|
|
5175
5240
|
const text = `Reflect skill draft revised.\n${buildPendingReflectSkillMessage(currentSession.planState)}`;
|
|
5176
5241
|
await persistLocalExchange(line, text);
|
|
5177
5242
|
return { type: 'system', text };
|
|
@@ -5209,6 +5274,7 @@ export async function createChatRuntime({
|
|
|
5209
5274
|
if (hasPendingReflectSkill(currentSession)) {
|
|
5210
5275
|
currentSession.planState = null;
|
|
5211
5276
|
executionMode = 'auto';
|
|
5277
|
+
if (onAgentEvent) onAgentEvent({ type: 'reflect:approval_cleared' });
|
|
5212
5278
|
const text = 'Reflect skill draft discarded.';
|
|
5213
5279
|
await persistLocalExchange(line, text, { includeUser: false });
|
|
5214
5280
|
return { type: 'system', text };
|
|
@@ -5668,6 +5734,12 @@ export async function createChatRuntime({
|
|
|
5668
5734
|
request: parsedReflect.request,
|
|
5669
5735
|
candidates
|
|
5670
5736
|
};
|
|
5737
|
+
if (onAgentEvent) {
|
|
5738
|
+
onAgentEvent({
|
|
5739
|
+
type: 'reflect:pending_approval',
|
|
5740
|
+
draft: buildPendingReflectSkillSnapshot(currentSession.planState)
|
|
5741
|
+
});
|
|
5742
|
+
}
|
|
5671
5743
|
const text = buildPendingReflectSkillMessage(currentSession.planState);
|
|
5672
5744
|
await persistLocalExchange(line, text);
|
|
5673
5745
|
return { type: 'system', text };
|
|
@@ -5735,7 +5807,7 @@ export async function createChatRuntime({
|
|
|
5735
5807
|
await resetConfig();
|
|
5736
5808
|
config = await loadConfig();
|
|
5737
5809
|
await syncRuntimeFromConfig({ model: resolveDefaultModel(config) });
|
|
5738
|
-
|
|
5810
|
+
syncCompactStateFromConfig();
|
|
5739
5811
|
compactState.mode = 'conservative';
|
|
5740
5812
|
compactState.autoEnabled = true;
|
|
5741
5813
|
const text = 'Config reset complete';
|