codemini-cli 0.1.18 → 0.2.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/README.md +6 -6
- package/package.json +1 -1
- package/skills/superpowers-lite/SKILL.md +10 -9
- package/src/core/agent-loop.js +63 -31
- package/src/core/chat-runtime.js +89 -16
- package/src/core/command-policy.js +4 -4
- package/src/core/config-store.js +16 -29
- package/src/core/provider/openai-compatible.js +9 -0
- package/src/core/shell-profile.js +1 -1
- package/src/core/shell.js +122 -2
- package/src/core/tools.js +527 -214
- package/src/tui/chat-app.js +849 -143
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@ It is designed for teams that want a coding assistant that feels practical, cont
|
|
|
19
19
|
|
|
20
20
|
### Highlights
|
|
21
21
|
|
|
22
|
-
- Minimal default tools: `
|
|
22
|
+
- Minimal default tools: `run`, `read`, `write`
|
|
23
23
|
- Windows-aware shell profile with PowerShell-focused defaults
|
|
24
24
|
- Safe mode enabled by default
|
|
25
25
|
- Built-in lite skills for planning, execution, and collaboration
|
|
26
26
|
- Configurable reply language through `ui.reply_language` (`zh` / `en`)
|
|
27
27
|
- Richer slash completion with priority sorting, inline descriptions, and left/right paging
|
|
28
|
-
- Structured code tools for small models: `
|
|
28
|
+
- Structured code tools for small models: `grep`, `read`, `edit`
|
|
29
29
|
- More conservative `plan auto` acceptance checks with reviewer/tester goal checklists
|
|
30
30
|
- Tone presets through `soul`, without changing plans or code behavior
|
|
31
31
|
- Example soul presets include `professional`, `playful`, `anime`, `pirate`, `caveman`, and `ceo`
|
|
@@ -66,7 +66,7 @@ codemini skill list|install|enable|disable|inspect|reindex
|
|
|
66
66
|
- Slash completion now prioritizes important commands and config keys, shows short descriptions, and supports `←/→` page switching
|
|
67
67
|
- Ambiguous feature requests can pause for lightweight brainstorming first, and `/brainstorm <question>` gives an explicit way to compare options before coding
|
|
68
68
|
- `plan auto` now turns the original goal into an acceptance checklist, uses a lighter chain only for truly tiny tasks, and treats unmet checklist items as failure signals
|
|
69
|
-
- Structured code tools reduce shell-noise for small models by preferring `
|
|
69
|
+
- Structured code tools reduce shell-noise for small models by preferring `grep/read -> edit`
|
|
70
70
|
|
|
71
71
|
### Skill Loading
|
|
72
72
|
|
|
@@ -134,13 +134,13 @@ CodeMini CLI 是一个为小模型工作流优化过的代码助手 CLI,重点
|
|
|
134
134
|
|
|
135
135
|
### 主要特点
|
|
136
136
|
|
|
137
|
-
- 默认工具极简:`
|
|
137
|
+
- 默认工具极简:`run`、`read`、`write`
|
|
138
138
|
- 面向 Windows 的 PowerShell 默认配置
|
|
139
139
|
- safe mode 默认开启
|
|
140
140
|
- 内置 lite skills,覆盖规划、执行和协作
|
|
141
141
|
- 支持通过 `ui.reply_language` 配置回复语言,当前支持 `zh` / `en`
|
|
142
142
|
- slash 补全支持优先级排序、右侧简短说明和左右分页
|
|
143
|
-
- 为小模型补了结构化代码工具:`
|
|
143
|
+
- 为小模型补了结构化代码工具:`grep`、`read`、`edit`
|
|
144
144
|
- `plan auto` 会基于原始目标生成验收清单,并更保守地处理 reviewer/tester 结果
|
|
145
145
|
- `soul` 只影响语气,不影响计划和代码行为
|
|
146
146
|
- 可用的 `soul` 示例包括 `professional`、`playful`、`anime`、`pirate`、`caveman`、`ceo`
|
|
@@ -181,7 +181,7 @@ codemini skill list|install|enable|disable|inspect|reindex
|
|
|
181
181
|
- slash 补全会优先展示更重要的命令和配置项,显示简短说明,并支持 `←/→` 翻页
|
|
182
182
|
- 对于需求仍不明确的功能请求,CLI 会先偏向轻量 brainstorm;也可以显式使用 `/brainstorm <问题>` 先比较方案再决定是否编码
|
|
183
183
|
- `plan auto` 会先把原始目标展开成验收清单;只有真正很小的任务才会走轻量链路;如果 reviewer 或 tester 标记了未满足或未验证的验收项,就不会按成功处理
|
|
184
|
-
- 为了减少小模型被 shell 原始输出干扰,新增了 `
|
|
184
|
+
- 为了减少小模型被 shell 原始输出干扰,新增了 `grep/read -> edit` 这套结构化代码工具流
|
|
185
185
|
|
|
186
186
|
### Skill 加载位置
|
|
187
187
|
|
package/package.json
CHANGED
|
@@ -34,16 +34,17 @@ Routing:
|
|
|
34
34
|
- do not assume features, storage model, or scope unless the user already gave them
|
|
35
35
|
|
|
36
36
|
Tool order:
|
|
37
|
-
- prefer `
|
|
38
|
-
- use `
|
|
39
|
-
- use `
|
|
40
|
-
- use `
|
|
37
|
+
- prefer `grep` first for content search and candidate discovery
|
|
38
|
+
- use `read` to inspect the smallest useful code block
|
|
39
|
+
- use `edit` for minimal focused edits or direct whole-file rewrites when you already have the replacement content
|
|
40
|
+
- use `generate_diff` and `patch` for larger edits or when you already have a diff
|
|
41
|
+
- use `glob` and `list` when you need file or directory discovery
|
|
41
42
|
- use shell search such as `rg` only as a fallback when structured tools are not enough
|
|
42
43
|
|
|
43
44
|
Core rules:
|
|
44
45
|
|
|
45
46
|
1. Search first.
|
|
46
|
-
Prefer structured search before broad file reads. Start with `
|
|
47
|
+
Prefer structured search before broad file reads. Start with `grep`, then inspect with `read`, and only fall back to shell search such as `rg` when the structured tools are not enough.
|
|
47
48
|
|
|
48
49
|
2. Keep context tight.
|
|
49
50
|
Do not carry full conversation history into every step. Summarize, narrow scope, and work from the most recent relevant evidence.
|
|
@@ -62,18 +63,18 @@ If the requested behavior, scope, or acceptance is unclear, do not jump into imp
|
|
|
62
63
|
- clear enough to build -> proceed
|
|
63
64
|
|
|
64
65
|
5. Read and write with intent.
|
|
65
|
-
Use `
|
|
66
|
+
Use `read` before broad reads when possible. Use `edit` for focused edits or when you already have the complete replacement content. Use `generate_diff` and `patch` for larger changes. Use `write` only for creating new files or explicit whole-file writes. Avoid unnecessary tool calls and avoid rereading the same file without a reason.
|
|
66
67
|
|
|
67
68
|
6. Verify before claiming success.
|
|
68
69
|
Run the relevant test, check, or command before saying work is fixed or complete.
|
|
69
70
|
|
|
70
71
|
Default workflow:
|
|
71
|
-
- Search with `
|
|
72
|
-
- Inspect local context with `
|
|
72
|
+
- Search with `grep`
|
|
73
|
+
- Inspect local context with `read`
|
|
73
74
|
- If the request is unclear, first decide: ask one question, brainstorm, or proceed
|
|
74
75
|
- Plan the next smallest step
|
|
75
76
|
- Delegate if the work is independent
|
|
76
|
-
- Edit with `
|
|
77
|
+
- Edit with `edit`
|
|
77
78
|
- Verify
|
|
78
79
|
- Summarize briefly
|
|
79
80
|
|
package/src/core/agent-loop.js
CHANGED
|
@@ -26,11 +26,19 @@ function summarizeToolResult(result) {
|
|
|
26
26
|
const p = String(obj.path || '');
|
|
27
27
|
const action = String(obj.action || 'write');
|
|
28
28
|
const line = Number(obj.changed_line || 1);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const suffix =
|
|
30
|
+
action === 'delete'
|
|
31
|
+
? 'deleted'
|
|
32
|
+
: action === 'create'
|
|
33
|
+
? 'created'
|
|
34
|
+
: action === 'patch'
|
|
35
|
+
? 'patched'
|
|
36
|
+
: action === 'replace_block' || action === 'replace_text'
|
|
37
|
+
? 'edited'
|
|
38
|
+
: action === 'append'
|
|
39
|
+
? 'appended'
|
|
40
|
+
: 'updated';
|
|
41
|
+
return p ? `${suffix} ${p}${line > 0 ? ` @L${line}` : ''}` : suffix;
|
|
34
42
|
}
|
|
35
43
|
if ('path' in obj && 'phase' in obj) {
|
|
36
44
|
const phase = String(obj.phase || '');
|
|
@@ -79,6 +87,13 @@ function summarizeToolResult(result) {
|
|
|
79
87
|
const logs = Array.isArray(obj.recent_logs) ? trimInline(obj.recent_logs.slice(-1)[0] || '', 96) : '';
|
|
80
88
|
return `${taskId || 'service logs'}${logs ? `\n${logs}` : ''}`;
|
|
81
89
|
}
|
|
90
|
+
if ('files' in obj && Array.isArray(obj.files)) {
|
|
91
|
+
return `patched ${obj.files.length} file(s)`;
|
|
92
|
+
}
|
|
93
|
+
if ('diff' in obj && 'new_hash' in obj && 'path' in obj) {
|
|
94
|
+
const p = String(obj.path || '');
|
|
95
|
+
return p ? `diff preview for ${p}` : 'diff preview';
|
|
96
|
+
}
|
|
82
97
|
if ('created' in obj && Array.isArray(obj.created)) {
|
|
83
98
|
return `created ${obj.created.length} task(s)`;
|
|
84
99
|
}
|
|
@@ -98,21 +113,45 @@ function trimInline(value, maxLen = 72) {
|
|
|
98
113
|
return `${s.slice(0, maxLen - 3)}...`;
|
|
99
114
|
}
|
|
100
115
|
|
|
116
|
+
function normalizeToolCallName(name) {
|
|
117
|
+
return String(name || '').trim();
|
|
118
|
+
}
|
|
119
|
+
|
|
101
120
|
function formatToolDisplayName(name, args) {
|
|
102
|
-
if (name === '
|
|
121
|
+
if (name === 'grep') {
|
|
122
|
+
const query = trimInline(args?.pattern || args?.query || args?.symbol || '', 96);
|
|
123
|
+
return query ? `grep("${query}")` : 'grep';
|
|
124
|
+
}
|
|
125
|
+
if (name === 'glob') {
|
|
126
|
+
const pattern = trimInline(args?.pattern || '', 96);
|
|
127
|
+
return pattern ? `glob("${pattern}")` : 'glob';
|
|
128
|
+
}
|
|
129
|
+
if (name === 'list') {
|
|
130
|
+
const target = trimInline(args?.path || '.', 96) || '.';
|
|
131
|
+
return `list(${target})`;
|
|
132
|
+
}
|
|
133
|
+
if (name === 'read' || name === 'write') {
|
|
103
134
|
const target = trimInline(args?.path || '.', 96) || '.';
|
|
104
|
-
if (name === '
|
|
135
|
+
if (name === 'read') {
|
|
105
136
|
const start = Number(args?.start_line);
|
|
106
137
|
const end = Number(args?.end_line);
|
|
107
138
|
const hasRange = Number.isFinite(start) && start > 0;
|
|
108
139
|
const suffix = hasRange ? `:${start}-${Number.isFinite(end) && end >= start ? end : start}` : '';
|
|
109
|
-
return
|
|
140
|
+
return `read(${target}${suffix})`;
|
|
110
141
|
}
|
|
111
|
-
return
|
|
142
|
+
return `write(${target})`;
|
|
112
143
|
}
|
|
113
|
-
if (name === '
|
|
144
|
+
if (name === 'run') {
|
|
114
145
|
const command = trimInline(args?.command || '', 96);
|
|
115
|
-
return command ?
|
|
146
|
+
return command ? `run(${command})` : name;
|
|
147
|
+
}
|
|
148
|
+
if (name === 'edit') {
|
|
149
|
+
const target = trimInline(args?.path || args?.file || '.', 96) || '.';
|
|
150
|
+
return `edit(${target})`;
|
|
151
|
+
}
|
|
152
|
+
if (name === 'patch') {
|
|
153
|
+
const target = trimInline(args?.path || args?.file || args?.patch || '', 96) || '.';
|
|
154
|
+
return `patch(${target})`;
|
|
116
155
|
}
|
|
117
156
|
if (name === 'start_service') {
|
|
118
157
|
const command = trimInline(args?.command || args?.cmd || '', 96);
|
|
@@ -125,20 +164,7 @@ function formatToolDisplayName(name, args) {
|
|
|
125
164
|
const taskId = trimInline(args?.task_id || args?.taskId || '', 96);
|
|
126
165
|
return taskId ? `${name}(${taskId})` : name;
|
|
127
166
|
}
|
|
128
|
-
if (
|
|
129
|
-
name === 'locate' ||
|
|
130
|
-
name === 'open_target' ||
|
|
131
|
-
name === 'edit_target' ||
|
|
132
|
-
name === 'search_code' ||
|
|
133
|
-
name === 'read_block' ||
|
|
134
|
-
name === 'read_symbol_context' ||
|
|
135
|
-
name === 'validate_edit' ||
|
|
136
|
-
name === 'replace_block' ||
|
|
137
|
-
name === 'replace_text' ||
|
|
138
|
-
name === 'insert_before' ||
|
|
139
|
-
name === 'insert_after' ||
|
|
140
|
-
name === 'generate_diff'
|
|
141
|
-
) {
|
|
167
|
+
if (name === 'read' || name === 'write' || name === 'run' || name === 'grep' || name === 'glob' || name === 'list' || name === 'edit' || name === 'patch' || name === 'generate_diff') {
|
|
142
168
|
const target = trimInline(args?.path || args?.query || args?.symbol || '', 96);
|
|
143
169
|
return target ? `${name}(${target})` : name;
|
|
144
170
|
}
|
|
@@ -218,15 +244,16 @@ export async function runAgentLoop({
|
|
|
218
244
|
|
|
219
245
|
for (const call of toolCalls) {
|
|
220
246
|
const args = safeJsonParse(call.arguments);
|
|
221
|
-
const
|
|
247
|
+
const toolName = normalizeToolCallName(call.name);
|
|
248
|
+
const displayName = formatToolDisplayName(toolName, args);
|
|
222
249
|
const startedAt = Date.now();
|
|
223
250
|
let approved = true;
|
|
224
|
-
if (executionMode === 'normal' && !alwaysAllowSet.has(
|
|
251
|
+
if (executionMode === 'normal' && !alwaysAllowSet.has(toolName)) {
|
|
225
252
|
approved = false;
|
|
226
253
|
if (typeof requestToolApproval === 'function') {
|
|
227
254
|
const decision = await requestToolApproval({
|
|
228
255
|
id: call.id,
|
|
229
|
-
name:
|
|
256
|
+
name: toolName,
|
|
230
257
|
displayName,
|
|
231
258
|
arguments: args
|
|
232
259
|
});
|
|
@@ -235,7 +262,7 @@ export async function runAgentLoop({
|
|
|
235
262
|
}
|
|
236
263
|
|
|
237
264
|
if (!approved) {
|
|
238
|
-
if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id });
|
|
265
|
+
if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id, arguments: args });
|
|
239
266
|
const blockedMessage = {
|
|
240
267
|
role: 'tool',
|
|
241
268
|
tool_call_id: call.id,
|
|
@@ -247,6 +274,7 @@ export async function runAgentLoop({
|
|
|
247
274
|
type: 'tool:result',
|
|
248
275
|
name: displayName,
|
|
249
276
|
id: call.id,
|
|
277
|
+
arguments: args,
|
|
250
278
|
content: blockedMessage.content,
|
|
251
279
|
blocked: true
|
|
252
280
|
});
|
|
@@ -254,8 +282,8 @@ export async function runAgentLoop({
|
|
|
254
282
|
continue;
|
|
255
283
|
}
|
|
256
284
|
|
|
257
|
-
if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id });
|
|
258
|
-
const handler = toolHandlers[
|
|
285
|
+
if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id, arguments: args });
|
|
286
|
+
const handler = toolHandlers[toolName];
|
|
259
287
|
if (!handler) {
|
|
260
288
|
throw new Error(`Unknown tool: ${call.name}`);
|
|
261
289
|
}
|
|
@@ -270,6 +298,7 @@ export async function runAgentLoop({
|
|
|
270
298
|
type: 'tool:error',
|
|
271
299
|
name: displayName,
|
|
272
300
|
id: call.id,
|
|
301
|
+
arguments: args,
|
|
273
302
|
durationMs,
|
|
274
303
|
summary: trimInline(message, 120)
|
|
275
304
|
});
|
|
@@ -285,6 +314,7 @@ export async function runAgentLoop({
|
|
|
285
314
|
type: 'tool:result',
|
|
286
315
|
name: displayName,
|
|
287
316
|
id: call.id,
|
|
317
|
+
arguments: args,
|
|
288
318
|
content: toolMessage.content,
|
|
289
319
|
error: true
|
|
290
320
|
});
|
|
@@ -297,6 +327,7 @@ export async function runAgentLoop({
|
|
|
297
327
|
type: 'tool:end',
|
|
298
328
|
name: displayName,
|
|
299
329
|
id: call.id,
|
|
330
|
+
arguments: args,
|
|
300
331
|
durationMs,
|
|
301
332
|
summary: summarizeToolResult(toolResult)
|
|
302
333
|
});
|
|
@@ -312,6 +343,7 @@ export async function runAgentLoop({
|
|
|
312
343
|
type: 'tool:result',
|
|
313
344
|
name: displayName,
|
|
314
345
|
id: call.id,
|
|
346
|
+
arguments: args,
|
|
315
347
|
content: toolMessage.content
|
|
316
348
|
});
|
|
317
349
|
}
|
package/src/core/chat-runtime.js
CHANGED
|
@@ -94,6 +94,8 @@ function describeConfigKey(key, mode = 'set') {
|
|
|
94
94
|
'shell.default': 'default shell',
|
|
95
95
|
'shell.timeout_ms': 'shell timeout in milliseconds',
|
|
96
96
|
'context.max_tokens': 'context token budget',
|
|
97
|
+
'soul.preset': 'soul preset',
|
|
98
|
+
'soul.custom_path': 'custom soul prompt path',
|
|
97
99
|
'policy.safe_mode': 'safe mode switch',
|
|
98
100
|
'policy.allow_dangerous_commands': 'dangerous command allowance'
|
|
99
101
|
};
|
|
@@ -1116,12 +1118,28 @@ function effectiveMaxContextTokens(config) {
|
|
|
1116
1118
|
}
|
|
1117
1119
|
|
|
1118
1120
|
function buildRuntimeStateSnapshot({ currentSession, config, model, executionMode }) {
|
|
1119
|
-
|
|
1121
|
+
const currentContextTokens = estimateMessagesTokens(currentSession?.messages || []);
|
|
1122
|
+
const maxContextTokens = effectiveMaxContextTokens(config);
|
|
1123
|
+
const contextUsagePct = maxContextTokens > 0 ? Math.min(100, Math.max(0, (currentContextTokens / maxContextTokens) * 100)) : 0;
|
|
1124
|
+
const snapshot = {
|
|
1120
1125
|
sessionId: currentSession?.id || '',
|
|
1121
1126
|
mode: executionMode || config.execution?.mode || 'auto',
|
|
1122
1127
|
model: model || config.model?.name || '',
|
|
1123
|
-
maxContextTokens
|
|
1128
|
+
maxContextTokens
|
|
1124
1129
|
};
|
|
1130
|
+
Object.defineProperties(snapshot, {
|
|
1131
|
+
currentContextTokens: {
|
|
1132
|
+
value: currentContextTokens,
|
|
1133
|
+
enumerable: false,
|
|
1134
|
+
writable: false
|
|
1135
|
+
},
|
|
1136
|
+
contextUsagePct: {
|
|
1137
|
+
value: contextUsagePct,
|
|
1138
|
+
enumerable: false,
|
|
1139
|
+
writable: false
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
return snapshot;
|
|
1125
1143
|
}
|
|
1126
1144
|
|
|
1127
1145
|
function estimatePromptTokensForRequest(sessionMessages, userText = '') {
|
|
@@ -1345,7 +1363,7 @@ async function askModel({
|
|
|
1345
1363
|
onEvent: wrappedAgentEvent,
|
|
1346
1364
|
executionMode: executionMode || config.execution?.mode || 'auto',
|
|
1347
1365
|
alwaysAllowTools:
|
|
1348
|
-
alwaysAllowTools || config.execution?.always_allow_tools || ['
|
|
1366
|
+
alwaysAllowTools || config.execution?.always_allow_tools || ['run', 'read', 'write'],
|
|
1349
1367
|
toolResultMaxChars: config.context?.tool_result_max_chars || 12000,
|
|
1350
1368
|
requestCompletion: async ({ messages, tools, model: selectedModel }) => {
|
|
1351
1369
|
if (onAgentEvent) onAgentEvent({ type: 'assistant:start' });
|
|
@@ -1359,6 +1377,9 @@ async function askModel({
|
|
|
1359
1377
|
maxRetries: config.gateway.max_retries ?? 2,
|
|
1360
1378
|
onTextDelta: (delta) => {
|
|
1361
1379
|
if (onAgentEvent) onAgentEvent({ type: 'assistant:delta', text: delta });
|
|
1380
|
+
},
|
|
1381
|
+
onToolCallDelta: (toolCall) => {
|
|
1382
|
+
if (onAgentEvent) onAgentEvent({ type: 'assistant:tool_call_delta', toolCall });
|
|
1362
1383
|
}
|
|
1363
1384
|
});
|
|
1364
1385
|
}
|
|
@@ -1720,6 +1741,8 @@ export async function createChatRuntime({
|
|
|
1720
1741
|
'sessions.retention_days',
|
|
1721
1742
|
'shell.timeout_ms',
|
|
1722
1743
|
'context.max_tokens',
|
|
1744
|
+
'soul.preset',
|
|
1745
|
+
'soul.custom_path',
|
|
1723
1746
|
'policy.safe_mode',
|
|
1724
1747
|
'policy.allow_dangerous_commands'
|
|
1725
1748
|
];
|
|
@@ -1851,9 +1874,21 @@ export async function createChatRuntime({
|
|
|
1851
1874
|
const body = input.slice(1);
|
|
1852
1875
|
const tokens = body.trim().split(/\s+/).filter(Boolean);
|
|
1853
1876
|
const commandPart = tokens[0] || '';
|
|
1877
|
+
const commandHasSubcommands = new Set([
|
|
1878
|
+
'config',
|
|
1879
|
+
'compact',
|
|
1880
|
+
'mode',
|
|
1881
|
+
'tasks',
|
|
1882
|
+
'checkpoint',
|
|
1883
|
+
'plan',
|
|
1884
|
+
'agents',
|
|
1885
|
+
'history',
|
|
1886
|
+
'debug'
|
|
1887
|
+
]);
|
|
1854
1888
|
|
|
1855
1889
|
const allCommandEntries = listCommandNames();
|
|
1856
1890
|
const allCommands = allCommandEntries.map((c) => c.name);
|
|
1891
|
+
const exactCommand = Boolean(commandPart) && allCommands.includes(commandPart);
|
|
1857
1892
|
for (const entry of allCommandEntries) {
|
|
1858
1893
|
registerSuggestion(`/${entry.name}`, entry.description || '');
|
|
1859
1894
|
}
|
|
@@ -1879,7 +1914,7 @@ export async function createChatRuntime({
|
|
|
1879
1914
|
));
|
|
1880
1915
|
}
|
|
1881
1916
|
|
|
1882
|
-
if (tokens.length === 1 && !hasTrailingSpace) {
|
|
1917
|
+
if (tokens.length === 1 && !hasTrailingSpace && !(exactCommand && commandHasSubcommands.has(commandPart))) {
|
|
1883
1918
|
const direct = prioritizeByPreferredOrder(
|
|
1884
1919
|
allCommands
|
|
1885
1920
|
.filter((name) => name.startsWith(commandPart))
|
|
@@ -1891,25 +1926,26 @@ export async function createChatRuntime({
|
|
|
1891
1926
|
}
|
|
1892
1927
|
|
|
1893
1928
|
if (commandPart === 'config') {
|
|
1894
|
-
|
|
1895
|
-
|
|
1929
|
+
const subcommand = tokens[1] || '';
|
|
1930
|
+
const subcommandIsExact = ['set', 'get', 'list', 'reset'].includes(subcommand);
|
|
1931
|
+
|
|
1932
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace && !subcommandIsExact)) {
|
|
1896
1933
|
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
1897
1934
|
['set', 'get', 'list', 'reset']
|
|
1898
|
-
.filter((s) => s.startsWith(
|
|
1935
|
+
.filter((s) => s.startsWith(subcommand))
|
|
1899
1936
|
.map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] || 'config command').value),
|
|
1900
1937
|
configSubcommandPriority
|
|
1901
1938
|
));
|
|
1902
1939
|
}
|
|
1903
1940
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
const keyPrefix = tokens[2] || '';
|
|
1941
|
+
if (subcommand === 'get') {
|
|
1942
|
+
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
1907
1943
|
return configKeyHints
|
|
1908
1944
|
.filter((k) => k.startsWith(keyPrefix))
|
|
1909
1945
|
.map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get')));
|
|
1910
1946
|
}
|
|
1911
|
-
if (
|
|
1912
|
-
const keyPrefix = tokens[2] || '';
|
|
1947
|
+
if (subcommand === 'set') {
|
|
1948
|
+
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
1913
1949
|
return configKeyHints
|
|
1914
1950
|
.filter((k) => k.startsWith(keyPrefix))
|
|
1915
1951
|
.map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set')));
|
|
@@ -1920,6 +1956,11 @@ export async function createChatRuntime({
|
|
|
1920
1956
|
|
|
1921
1957
|
if (commandPart === 'compact') {
|
|
1922
1958
|
const joined = tokens.slice(1).join(' ');
|
|
1959
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1960
|
+
return compactOptions
|
|
1961
|
+
.filter((opt) => opt.startsWith(joined) || joined === '')
|
|
1962
|
+
.map((opt) => registerSuggestion(`/compact ${opt}`, 'context compaction command'));
|
|
1963
|
+
}
|
|
1923
1964
|
return compactOptions
|
|
1924
1965
|
.filter((opt) => opt.includes(joined) || joined === '')
|
|
1925
1966
|
.map((opt) => registerSuggestion(`/compact ${opt}`, 'context compaction command'));
|
|
@@ -1952,6 +1993,10 @@ export async function createChatRuntime({
|
|
|
1952
1993
|
if (commandPart === 'checkpoint') {
|
|
1953
1994
|
if (tokens.length <= 2 && !hasTrailingSpace) {
|
|
1954
1995
|
const sub = tokens[1] || '';
|
|
1996
|
+
if (sub === 'list') {
|
|
1997
|
+
return ['--all']
|
|
1998
|
+
.map((v) => registerSuggestion(`/checkpoint list ${v}`, 'checkpoint command'));
|
|
1999
|
+
}
|
|
1955
2000
|
return ['create', 'list', 'load']
|
|
1956
2001
|
.filter((s) => s.startsWith(sub))
|
|
1957
2002
|
.map((s) => registerSuggestion(`/checkpoint ${s}`, 'checkpoint command'));
|
|
@@ -1987,6 +2032,10 @@ export async function createChatRuntime({
|
|
|
1987
2032
|
if (commandPart === 'agents') {
|
|
1988
2033
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1989
2034
|
const sub = tokens[1] || '';
|
|
2035
|
+
if (sub === 'run') {
|
|
2036
|
+
return ['planner', 'coder', 'reviewer', 'tester']
|
|
2037
|
+
.map((r) => registerSuggestion(`/agents run ${r} `, 'sub-agent command'));
|
|
2038
|
+
}
|
|
1990
2039
|
return ['list', 'run']
|
|
1991
2040
|
.filter((s) => s.startsWith(sub))
|
|
1992
2041
|
.map((s) => registerSuggestion(`/agents ${s}`, 'sub-agent command'));
|
|
@@ -2001,13 +2050,22 @@ export async function createChatRuntime({
|
|
|
2001
2050
|
}
|
|
2002
2051
|
|
|
2003
2052
|
if (commandPart === 'history') {
|
|
2053
|
+
const sub = tokens[1] || '';
|
|
2004
2054
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2005
|
-
|
|
2055
|
+
if (sub === 'resume') {
|
|
2056
|
+
const dynamic = historySessionCache
|
|
2057
|
+
.filter((session) => String(session.id || '').startsWith(''))
|
|
2058
|
+
.map((session) => ({
|
|
2059
|
+
value: `/history resume ${session.id}`,
|
|
2060
|
+
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
|
|
2061
|
+
description: 'resume a saved session'
|
|
2062
|
+
}));
|
|
2063
|
+
if (dynamic.length > 0) return dynamic;
|
|
2064
|
+
}
|
|
2006
2065
|
return ['list', 'current', 'resume']
|
|
2007
2066
|
.filter((s) => s.startsWith(sub))
|
|
2008
|
-
.map((s) => `/history ${s}
|
|
2067
|
+
.map((s) => registerSuggestion(`/history ${s}`, 'history command'));
|
|
2009
2068
|
}
|
|
2010
|
-
const sub = tokens[1] || '';
|
|
2011
2069
|
if (sub === 'resume') {
|
|
2012
2070
|
const idPrefix = tokens[2] || '';
|
|
2013
2071
|
const dynamic = historySessionCache
|
|
@@ -2025,7 +2083,15 @@ export async function createChatRuntime({
|
|
|
2025
2083
|
|
|
2026
2084
|
if (commandPart === 'debug') {
|
|
2027
2085
|
const sub = tokens[1] || '';
|
|
2028
|
-
if (!
|
|
2086
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2087
|
+
if (sub === 'keys') {
|
|
2088
|
+
return ['on', 'off', 'status']
|
|
2089
|
+
.map((v) => registerSuggestion(`/debug keys ${v}`, 'keyboard debug command'));
|
|
2090
|
+
}
|
|
2091
|
+
return ['keys']
|
|
2092
|
+
.filter((s) => s.startsWith(sub))
|
|
2093
|
+
.map((s) => registerSuggestion(`/debug ${s}`, 'debug command'));
|
|
2094
|
+
}
|
|
2029
2095
|
if (sub === 'keys') {
|
|
2030
2096
|
const action = tokens[2] || '';
|
|
2031
2097
|
return ['on', 'off', 'status']
|
|
@@ -2609,6 +2675,13 @@ export async function createChatRuntime({
|
|
|
2609
2675
|
}
|
|
2610
2676
|
|
|
2611
2677
|
const expandedText = await expandFileMentions(parsedInput.text, process.cwd());
|
|
2678
|
+
const selectedAutoSkills = selectAutoSkillNames(expandedText).filter((name) => isSkillEnabled(config, name));
|
|
2679
|
+
if (selectedAutoSkills.length > 0 && onAgentEvent) {
|
|
2680
|
+
onAgentEvent({
|
|
2681
|
+
type: 'skill:auto',
|
|
2682
|
+
names: selectedAutoSkills
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2612
2685
|
const routedSystemPrompt = buildAutoSkillSystemPrompt(activeReplySystemPrompt, commands, config, expandedText);
|
|
2613
2686
|
const result = await askModel({
|
|
2614
2687
|
text: expandedText,
|
|
@@ -16,13 +16,13 @@ function suggestionForToken(token, config) {
|
|
|
16
16
|
const shell = String(config?.shell?.default || '').toLowerCase();
|
|
17
17
|
if (token === 'find' || token === 'grep') {
|
|
18
18
|
return shell === 'powershell'
|
|
19
|
-
? 'Prefer structured tools like
|
|
20
|
-
: 'Prefer structured tools like
|
|
19
|
+
? 'Prefer structured tools like grep, list, read, and edit first. If you need shell fallback, use allowed search and context commands such as Get-ChildItem, Select-String, Get-Content, or rg when available.'
|
|
20
|
+
: 'Prefer structured tools like grep, glob, list, read, and edit first. If you need shell fallback, use allowed search and context commands such as rg, find, grep, sed, cat, or ls.';
|
|
21
21
|
}
|
|
22
22
|
if (shell === 'powershell') {
|
|
23
|
-
return 'Prefer structured tools like
|
|
23
|
+
return 'Prefer structured tools like read, edit, write, grep, and list first. If you need shell fallback, use allowed shell commands for search and local context such as Get-ChildItem, Get-Content, Select-String, or rg when available.';
|
|
24
24
|
}
|
|
25
|
-
return 'Prefer structured tools like
|
|
25
|
+
return 'Prefer structured tools like read, edit, write, grep, glob, and list first. If you need shell fallback, use allowed shell commands for search and local context such as rg, find, grep, sed, cat, or ls.';
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function evaluateCommandPolicy(command, config, workspaceRoot = process.cwd()) {
|
package/src/core/config-store.js
CHANGED
|
@@ -34,26 +34,20 @@ const DEFAULT_CONFIG = {
|
|
|
34
34
|
execution: {
|
|
35
35
|
mode: 'auto',
|
|
36
36
|
always_allow_tools: [
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'replace_text',
|
|
46
|
-
'insert_before',
|
|
47
|
-
'insert_after',
|
|
37
|
+
'read',
|
|
38
|
+
'grep',
|
|
39
|
+
'glob',
|
|
40
|
+
'list',
|
|
41
|
+
'edit',
|
|
42
|
+
'write',
|
|
43
|
+
'run',
|
|
44
|
+
'patch',
|
|
48
45
|
'generate_diff',
|
|
49
46
|
'start_service',
|
|
50
47
|
'list_services',
|
|
51
48
|
'get_service_status',
|
|
52
49
|
'get_service_logs',
|
|
53
|
-
'stop_service'
|
|
54
|
-
'run_command',
|
|
55
|
-
'read_file',
|
|
56
|
-
'write_file'
|
|
50
|
+
'stop_service'
|
|
57
51
|
],
|
|
58
52
|
max_steps: 16
|
|
59
53
|
},
|
|
@@ -134,26 +128,19 @@ function normalizePolicyLists(config) {
|
|
|
134
128
|
: [];
|
|
135
129
|
next.execution.always_allow_tools = uniqueStrings(
|
|
136
130
|
[
|
|
137
|
-
'
|
|
138
|
-
'
|
|
139
|
-
'
|
|
140
|
-
'
|
|
141
|
-
'
|
|
142
|
-
'
|
|
143
|
-
'
|
|
144
|
-
'replace_block',
|
|
145
|
-
'replace_text',
|
|
146
|
-
'insert_before',
|
|
147
|
-
'insert_after',
|
|
131
|
+
'read',
|
|
132
|
+
'grep',
|
|
133
|
+
'glob',
|
|
134
|
+
'list',
|
|
135
|
+
'edit',
|
|
136
|
+
'write',
|
|
137
|
+
'run',
|
|
148
138
|
'generate_diff',
|
|
149
139
|
'start_service',
|
|
150
140
|
'list_services',
|
|
151
141
|
'get_service_status',
|
|
152
142
|
'get_service_logs',
|
|
153
143
|
'stop_service',
|
|
154
|
-
'run_command',
|
|
155
|
-
'read_file',
|
|
156
|
-
'write_file',
|
|
157
144
|
...rawTools
|
|
158
145
|
].filter((name) => String(name) !== 'list_files')
|
|
159
146
|
);
|
|
@@ -205,6 +205,7 @@ export async function createChatCompletionStream({
|
|
|
205
205
|
temperature = 0.2,
|
|
206
206
|
tools,
|
|
207
207
|
onTextDelta,
|
|
208
|
+
onToolCallDelta,
|
|
208
209
|
timeoutMs = 90000,
|
|
209
210
|
maxRetries = 2
|
|
210
211
|
}) {
|
|
@@ -248,6 +249,14 @@ export async function createChatCompletionStream({
|
|
|
248
249
|
if (td.function?.name) current.name = `${current.name}${td.function.name}`;
|
|
249
250
|
if (td.function?.arguments) current.arguments = `${current.arguments}${td.function.arguments}`;
|
|
250
251
|
toolCallsByIndex.set(idx, current);
|
|
252
|
+
if (onToolCallDelta) {
|
|
253
|
+
onToolCallDelta({
|
|
254
|
+
index: idx,
|
|
255
|
+
id: current.id || `tc-${idx + 1}`,
|
|
256
|
+
name: current.name,
|
|
257
|
+
arguments: current.arguments || '{}'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
251
260
|
}
|
|
252
261
|
}
|
|
253
262
|
|
|
@@ -118,5 +118,5 @@ export function getEffectivePolicy(config) {
|
|
|
118
118
|
|
|
119
119
|
export function getShellSystemPrompt(value) {
|
|
120
120
|
const profile = getShellProfile(value);
|
|
121
|
-
return `You are CodeMini CLI working in a ${profile.label} shell environment. Prefer
|
|
121
|
+
return `You are CodeMini CLI working in a ${profile.label} shell environment. Prefer OpenCode-style primary tools first: use read to inspect files, grep to search file contents, glob to find files by pattern, list to inspect directories, edit to modify existing files, write to create or fully rewrite files when appropriate, patch to apply unified diffs, and run for one-shot shell commands like install, build, test, or other finite tasks. Classify frontend, backend, database, and Docker work carefully: use run for finite commands, and use start_service, list_services, get_service_status, get_service_logs, and stop_service for long-running servers, watchers, and dev processes. Treat edit as the default editing path for existing code. Internal low-level edit strategies such as target resolution, block replacement, exact text replacement, and anchored inserts are handled inside edit rather than exposed as separate tools. Use generate_diff when you need a structured preview of a proposed file change. For existing code files, prefer grep/read/edit and only use write with full_file_rewrite=true when a whole-file rewrite is truly intended. Avoid unnecessary tool calls.`;
|
|
122
122
|
}
|