codemini-cli 0.3.1 → 0.3.3
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/OPERATIONS.md +4 -0
- package/README.md +199 -133
- package/package.json +2 -1
- package/src/commands/chat.js +1 -0
- package/src/commands/run.js +6 -2
- package/src/core/agent-loop.js +20 -19
- package/src/core/chat-runtime.js +567 -233
- package/src/core/checkpoint-store.js +2 -3
- package/src/core/command-policy.js +144 -10
- package/src/core/config-store.js +36 -10
- package/src/core/context-compact.js +7 -1
- package/src/core/default-system-prompt.js +12 -1
- package/src/core/memory-policy.js +33 -0
- package/src/core/memory-prompt.js +45 -0
- package/src/core/memory-store.js +181 -0
- package/src/core/paths.js +8 -0
- package/src/core/provider/anthropic.js +388 -0
- package/src/core/provider/index.js +37 -0
- package/src/core/session-store.js +4 -0
- package/src/core/shell-profile.js +29 -17
- package/src/core/todo-state.js +19 -0
- package/src/core/tools.js +486 -235
- package/src/tui/chat-app.js +278 -57
- package/src/tui/tool-activity/presenters/command.js +8 -15
- package/src/tui/tool-activity/presenters/misc.js +2 -5
- package/src/core/task-store.js +0 -117
package/src/core/chat-runtime.js
CHANGED
|
@@ -6,20 +6,13 @@ import path from 'node:path';
|
|
|
6
6
|
import {
|
|
7
7
|
createChatCompletion,
|
|
8
8
|
createChatCompletionStream
|
|
9
|
-
} from './provider/
|
|
9
|
+
} from './provider/index.js';
|
|
10
10
|
import { isDangerousCommand, runShellCommand } from './shell.js';
|
|
11
11
|
import { getBuiltinTools } from './tools.js';
|
|
12
12
|
import { listSessions, loadSession, pruneSessions, saveSession } from './session-store.js';
|
|
13
13
|
import { getConfigValue, loadConfig, resetConfig, setConfigValue } from './config-store.js';
|
|
14
14
|
import { evaluateCommandPolicy } from './command-policy.js';
|
|
15
15
|
import { appendInputHistory, loadInputHistory } from './input-history-store.js';
|
|
16
|
-
import {
|
|
17
|
-
clearTasks,
|
|
18
|
-
createTasks,
|
|
19
|
-
deleteTasks,
|
|
20
|
-
loadTasks,
|
|
21
|
-
updateTask
|
|
22
|
-
} from './task-store.js';
|
|
23
16
|
import { createCheckpoint, listCheckpoints, loadCheckpoint } from './checkpoint-store.js';
|
|
24
17
|
import {
|
|
25
18
|
compactMessagesLocally,
|
|
@@ -30,6 +23,9 @@ import { buildSystemPromptWithReplyLanguage } from './reply-language.js';
|
|
|
30
23
|
import { buildSystemPromptWithSoul } from './soul.js';
|
|
31
24
|
import { getProjectPlansDir, getProjectSpecsDir, getProjectWorkspaceDir, getSessionsDir } from './paths.js';
|
|
32
25
|
import { buildProjectContextSnippet, initializeProjectIndex } from './project-index.js';
|
|
26
|
+
import { buildMemorySnapshot } from './memory-prompt.js';
|
|
27
|
+
import { forgetMemory, listMemories, searchMemories } from './memory-store.js';
|
|
28
|
+
import { countActiveTodos, normalizeTodos } from './todo-state.js';
|
|
33
29
|
|
|
34
30
|
function toOpenAIMessages(sessionMessages) {
|
|
35
31
|
const mapped = [];
|
|
@@ -74,35 +70,195 @@ function prioritizeByPreferredOrder(items, preferredOrder) {
|
|
|
74
70
|
});
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
73
|
+
function normalizeUiLocale(value) {
|
|
74
|
+
return String(value || '').toLowerCase().startsWith('en') ? 'en' : 'zh';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getCompletionCopy(language = 'zh') {
|
|
78
|
+
const lang = normalizeUiLocale(language);
|
|
79
|
+
return {
|
|
80
|
+
zh: {
|
|
81
|
+
configLabels: {
|
|
82
|
+
'gateway.base_url': '网关基础 URL',
|
|
83
|
+
'gateway.api_key': '网关 API Key',
|
|
84
|
+
'sdk.provider': 'SDK provider',
|
|
85
|
+
'gateway.timeout_ms': '网关超时时间(毫秒)',
|
|
86
|
+
'gateway.max_retries': '网关重试次数',
|
|
87
|
+
'model.name': '当前模型名称',
|
|
88
|
+
'model.max_context_tokens': '模型上下文 token 上限',
|
|
89
|
+
'ui.language': '界面语言',
|
|
90
|
+
'ui.reply_language': '回复语言',
|
|
91
|
+
'execution.mode': '执行模式',
|
|
92
|
+
'execution.always_allow_tools': '始终允许的工具列表',
|
|
93
|
+
'execution.max_steps': '最大工具步骤数',
|
|
94
|
+
'context.preflight_trigger_pct': '预压缩阈值',
|
|
95
|
+
'context.hard_limit_pct': '硬压缩阈值',
|
|
96
|
+
'context.tool_result_max_chars': '工具结果字符上限',
|
|
97
|
+
'context.read_file_default_lines': 'read_file 默认行数窗口',
|
|
98
|
+
'context.read_file_max_chars': 'read_file 字符上限',
|
|
99
|
+
'sessions.max_sessions': '会话保留上限',
|
|
100
|
+
'sessions.retention_days': '会话保留天数',
|
|
101
|
+
'shell.default': '默认 shell',
|
|
102
|
+
'shell.timeout_ms': 'shell 超时时间(毫秒)',
|
|
103
|
+
'context.max_tokens': '上下文 token 预算',
|
|
104
|
+
'soul.preset': 'soul 预设',
|
|
105
|
+
'soul.custom_path': '自定义 soul 路径',
|
|
106
|
+
'policy.safe_mode': '安全模式开关',
|
|
107
|
+
'policy.allow_dangerous_commands': '危险命令开关'
|
|
108
|
+
},
|
|
109
|
+
optionHints: {
|
|
110
|
+
'sdk.provider': '可选:openai-compatible | anthropic',
|
|
111
|
+
'ui.language': '可选:zh | en',
|
|
112
|
+
'ui.reply_language': '可选:zh | en',
|
|
113
|
+
'execution.mode': '可选:auto | normal | plan',
|
|
114
|
+
'shell.default': '常用:bash | powershell',
|
|
115
|
+
'policy.safe_mode': '可选:true | false',
|
|
116
|
+
'policy.allow_dangerous_commands': '可选:true | false'
|
|
117
|
+
},
|
|
118
|
+
describeSet: (label, hint) => `设置${label}${hint ? `(${hint})` : ''}`,
|
|
119
|
+
describeGet: (label, hint) => `查看${label}${hint ? `(${hint})` : ''}`,
|
|
120
|
+
configSubcommands: {
|
|
121
|
+
'/config set': '设置配置项',
|
|
122
|
+
'/config get': '查看配置项',
|
|
123
|
+
'/config list': '查看完整配置',
|
|
124
|
+
'/config reset': '重置为默认配置'
|
|
125
|
+
},
|
|
126
|
+
planSubcommands: {
|
|
127
|
+
'/plan <goal>': '创建一个人工审阅的实施计划',
|
|
128
|
+
'/plan auto <goal>': '自动生成计划并等待你确认执行',
|
|
129
|
+
'/plan auto run <goal>': '自动生成计划后立即继续执行',
|
|
130
|
+
'/plan approve': '批准当前待确认的计划并开始执行',
|
|
131
|
+
'/plan from-spec <spec-path?>': '从 spec 文件生成实施计划'
|
|
132
|
+
},
|
|
133
|
+
commands: {
|
|
134
|
+
help: '显示聊天帮助',
|
|
135
|
+
exit: '退出聊天',
|
|
136
|
+
commands: '列出 slash/自定义命令',
|
|
137
|
+
status: '查看运行状态(mode/model/session)',
|
|
138
|
+
mode: '设置执行模式:normal|auto|plan',
|
|
139
|
+
compact: '压缩消息上下文',
|
|
140
|
+
checkpoint: '创建/查看/加载检查点',
|
|
141
|
+
spec: '在 .codemini/specs 中创建 spec',
|
|
142
|
+
plan: '在 .codemini/plans 中创建实施计划',
|
|
143
|
+
agents: '列出/运行子代理角色',
|
|
144
|
+
config: '设置/读取/列出/重置配置',
|
|
145
|
+
memory: '查看/搜索/删除持久记忆',
|
|
146
|
+
history: '查看/恢复会话',
|
|
147
|
+
debug: '运行时调试开关',
|
|
148
|
+
retry: '重试上一条用户请求'
|
|
149
|
+
},
|
|
150
|
+
generic: {
|
|
151
|
+
configCommand: '配置命令',
|
|
152
|
+
historyCommand: '历史会话命令',
|
|
153
|
+
modeCommand: '切换执行模式',
|
|
154
|
+
checkpointCommand: '检查点命令',
|
|
155
|
+
specCommand: '创建 spec 文件',
|
|
156
|
+
planCommand: '规划命令',
|
|
157
|
+
agentCommand: '子代理命令',
|
|
158
|
+
memoryCommand: '记忆命令',
|
|
159
|
+
debugCommand: '调试命令',
|
|
160
|
+
keyboardDebugCommand: '键盘调试命令',
|
|
161
|
+
compactCommand: '上下文压缩命令',
|
|
162
|
+
retryCommand: '重试上一条用户请求',
|
|
163
|
+
statusCommand: '查看运行状态',
|
|
164
|
+
resumeSession: '恢复一个已保存的会话'
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
en: {
|
|
168
|
+
configLabels: {
|
|
169
|
+
'gateway.base_url': 'gateway base URL',
|
|
170
|
+
'gateway.api_key': 'gateway API key',
|
|
171
|
+
'sdk.provider': 'SDK provider',
|
|
172
|
+
'gateway.timeout_ms': 'gateway timeout in milliseconds',
|
|
173
|
+
'gateway.max_retries': 'gateway retry count',
|
|
174
|
+
'model.name': 'active model name',
|
|
175
|
+
'model.max_context_tokens': 'model context token limit',
|
|
176
|
+
'ui.language': 'UI language',
|
|
177
|
+
'ui.reply_language': 'reply language',
|
|
178
|
+
'execution.mode': 'execution mode',
|
|
179
|
+
'execution.always_allow_tools': 'always-allowed tools',
|
|
180
|
+
'execution.max_steps': 'maximum tool steps',
|
|
181
|
+
'context.preflight_trigger_pct': 'preflight compact threshold',
|
|
182
|
+
'context.hard_limit_pct': 'hard compact threshold',
|
|
183
|
+
'context.tool_result_max_chars': 'tool result character limit',
|
|
184
|
+
'context.read_file_default_lines': 'default read_file line window',
|
|
185
|
+
'context.read_file_max_chars': 'read_file character limit',
|
|
186
|
+
'sessions.max_sessions': 'stored session limit',
|
|
187
|
+
'sessions.retention_days': 'session retention days',
|
|
188
|
+
'shell.default': 'default shell',
|
|
189
|
+
'shell.timeout_ms': 'shell timeout in milliseconds',
|
|
190
|
+
'context.max_tokens': 'context token budget',
|
|
191
|
+
'soul.preset': 'soul preset',
|
|
192
|
+
'soul.custom_path': 'custom soul prompt path',
|
|
193
|
+
'policy.safe_mode': 'safe mode switch',
|
|
194
|
+
'policy.allow_dangerous_commands': 'dangerous command allowance'
|
|
195
|
+
},
|
|
196
|
+
optionHints: {
|
|
197
|
+
'sdk.provider': 'options: openai-compatible | anthropic',
|
|
198
|
+
'ui.language': 'options: zh | en',
|
|
199
|
+
'ui.reply_language': 'options: zh | en',
|
|
200
|
+
'execution.mode': 'options: auto | normal | plan',
|
|
201
|
+
'shell.default': 'common: bash | powershell',
|
|
202
|
+
'policy.safe_mode': 'options: true | false',
|
|
203
|
+
'policy.allow_dangerous_commands': 'options: true | false'
|
|
204
|
+
},
|
|
205
|
+
describeSet: (label, hint) => `set the ${label}${hint ? ` (${hint})` : ''}`,
|
|
206
|
+
describeGet: (label, hint) => `show the ${label}${hint ? ` (${hint})` : ''}`,
|
|
207
|
+
configSubcommands: {
|
|
208
|
+
'/config set': 'update a config value',
|
|
209
|
+
'/config get': 'show a config value',
|
|
210
|
+
'/config list': 'print the full config',
|
|
211
|
+
'/config reset': 'reset config to defaults'
|
|
212
|
+
},
|
|
213
|
+
planSubcommands: {
|
|
214
|
+
'/plan <goal>': 'create an implementation plan for manual review',
|
|
215
|
+
'/plan auto <goal>': 'generate a plan and wait for your approval',
|
|
216
|
+
'/plan auto run <goal>': 'generate a plan and continue execution immediately',
|
|
217
|
+
'/plan approve': 'approve the pending plan and start execution',
|
|
218
|
+
'/plan from-spec <spec-path?>': 'generate an implementation plan from a spec file'
|
|
219
|
+
},
|
|
220
|
+
commands: {
|
|
221
|
+
help: 'show chat help',
|
|
222
|
+
exit: 'exit chat',
|
|
223
|
+
commands: 'list slash/custom commands',
|
|
224
|
+
status: 'show runtime status (mode/model/session)',
|
|
225
|
+
mode: 'set execution mode: normal|auto|plan',
|
|
226
|
+
compact: 'compress message context',
|
|
227
|
+
checkpoint: 'create/list/load conversation checkpoints',
|
|
228
|
+
spec: 'create a spec markdown file in .codemini/specs',
|
|
229
|
+
plan: 'create an implementation plan markdown file in .codemini/plans',
|
|
230
|
+
agents: 'run/list sub-agent roles',
|
|
231
|
+
config: 'set/get/list/reset config values',
|
|
232
|
+
memory: 'list/search/delete persistent memories',
|
|
233
|
+
history: 'list/resume sessions',
|
|
234
|
+
debug: 'runtime debug switches',
|
|
235
|
+
retry: 'retry the last user request'
|
|
236
|
+
},
|
|
237
|
+
generic: {
|
|
238
|
+
configCommand: 'config command',
|
|
239
|
+
historyCommand: 'history command',
|
|
240
|
+
modeCommand: 'switch execution mode',
|
|
241
|
+
checkpointCommand: 'checkpoint command',
|
|
242
|
+
specCommand: 'create a spec file',
|
|
243
|
+
planCommand: 'planning command',
|
|
244
|
+
agentCommand: 'sub-agent command',
|
|
245
|
+
memoryCommand: 'memory command',
|
|
246
|
+
debugCommand: 'debug command',
|
|
247
|
+
keyboardDebugCommand: 'keyboard debug command',
|
|
248
|
+
compactCommand: 'context compaction command',
|
|
249
|
+
retryCommand: 'retry the last user request',
|
|
250
|
+
statusCommand: 'show runtime status',
|
|
251
|
+
resumeSession: 'resume a saved session'
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}[lang];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function describeConfigKey(key, mode = 'set', language = 'zh') {
|
|
258
|
+
const copy = getCompletionCopy(language);
|
|
259
|
+
const label = copy.configLabels[key] || key;
|
|
260
|
+
const hint = copy.optionHints[key] || '';
|
|
261
|
+
return mode === 'get' ? copy.describeGet(label, hint) : copy.describeSet(label, hint);
|
|
106
262
|
}
|
|
107
263
|
|
|
108
264
|
const SUB_AGENT_ROLES = ['planner', 'coder', 'reviewer', 'tester'];
|
|
@@ -110,7 +266,7 @@ const SUB_AGENT_CONTEXT_MAX_MESSAGES = 4;
|
|
|
110
266
|
const SUB_AGENT_CONTEXT_MAX_CHARS = 1200;
|
|
111
267
|
const SUB_AGENT_EVIDENCE_MAX_ITEMS = 3;
|
|
112
268
|
const SUB_AGENT_HANDOFF_MAX_ITEMS = 6;
|
|
113
|
-
function getSubAgentRolePrompt(role) {
|
|
269
|
+
export function getSubAgentRolePrompt(role) {
|
|
114
270
|
if (role === 'planner') {
|
|
115
271
|
return 'You are a planning sub-agent. Produce a concrete implementation plan with risks and verification.';
|
|
116
272
|
}
|
|
@@ -149,7 +305,11 @@ function getSubAgentRolePrompt(role) {
|
|
|
149
305
|
'- <single best next step>'
|
|
150
306
|
].join('\n');
|
|
151
307
|
}
|
|
152
|
-
return
|
|
308
|
+
return [
|
|
309
|
+
'You are an execution sub-agent. Produce practical implementation guidance with code-level detail.',
|
|
310
|
+
'Stop when: you have produced the code change and verified it compiles/passes basic checks.',
|
|
311
|
+
'If blocked: report what blocked you and what you tried, then stop.'
|
|
312
|
+
].join('\n');
|
|
153
313
|
}
|
|
154
314
|
|
|
155
315
|
function trimInlineText(value, maxLen = 220) {
|
|
@@ -407,6 +567,24 @@ function isLightweightAutoPlanGoal(goal, requirements = []) {
|
|
|
407
567
|
return /\b(add|update|fix|rename|trim|export|create|remove|change|implement)\b/i.test(text);
|
|
408
568
|
}
|
|
409
569
|
|
|
570
|
+
function classifyPlanTaskClass(goal = '') {
|
|
571
|
+
const text = String(goal || '').trim();
|
|
572
|
+
const lowerGoal = text.toLowerCase();
|
|
573
|
+
const advisory =
|
|
574
|
+
/\b(analyze|analysis|review|audit|inspect|assess|recommend|recommendation|optimization|optimize|improve|suggest|brainstorm|plan|feedback)\b/i.test(lowerGoal) ||
|
|
575
|
+
/(分析|审查|审计|检查|评估|建议|优化|优化点|优化建议|改进|改进点|规划|方案|看一下|看看|有哪些问题|有什么问题)/.test(text);
|
|
576
|
+
const implementation =
|
|
577
|
+
/\b(add|build|create|implement|support|introduce|refactor|rewrite|rework|migrate|change|update|fix)\b/i.test(lowerGoal) ||
|
|
578
|
+
/(新增|增加|实现|支持|重构|重写|改造|迁移|修改|更新|修复)/.test(text);
|
|
579
|
+
const verificationHeavy =
|
|
580
|
+
/\b(test|verify|validation|validate|prove|confirm|reproduce|check coverage)\b/i.test(lowerGoal) ||
|
|
581
|
+
/(测试|验证|校验|确认|复现|覆盖率)/.test(text);
|
|
582
|
+
|
|
583
|
+
if (verificationHeavy) return 'verification-heavy';
|
|
584
|
+
if (advisory && !implementation) return 'advisory';
|
|
585
|
+
return 'implementation';
|
|
586
|
+
}
|
|
587
|
+
|
|
410
588
|
function buildGoalRequirementPacket(goal, role) {
|
|
411
589
|
const rawGoal = trimInlineText(goal, 800);
|
|
412
590
|
if (!rawGoal) return '';
|
|
@@ -441,7 +619,9 @@ function buildAutoPlanPlannerGuidance() {
|
|
|
441
619
|
'- Prefer the smallest local approach that satisfies the goal.',
|
|
442
620
|
'- Do not output multiple alternative branches in the final plan.',
|
|
443
621
|
'- Do not assume implementation should begin before the plan is coherent.',
|
|
444
|
-
'-
|
|
622
|
+
'- Available sub-agent roles are planner, coder, reviewer, and tester. Use only the roles the task actually needs.',
|
|
623
|
+
'- For implementation-heavy or risky changes, prefer adding review and/or verification steps.',
|
|
624
|
+
'- For analysis, recommendation, or planning-only goals, you may omit reviewer/tester if they do not add value.',
|
|
445
625
|
'- Prefer 3-5 steps total unless the task is clearly larger.',
|
|
446
626
|
'- Keep the plan ordered, implementation-oriented, and easy for small sub-agents to follow.'
|
|
447
627
|
].join('\n');
|
|
@@ -598,49 +778,56 @@ function selectAutoSkillNames(text = '') {
|
|
|
598
778
|
return selected;
|
|
599
779
|
}
|
|
600
780
|
|
|
601
|
-
function
|
|
781
|
+
function classifyTaskComplexity(text = '') {
|
|
602
782
|
const input = String(text || '').trim();
|
|
603
|
-
if (!input) return
|
|
783
|
+
if (!input) return 'simple';
|
|
604
784
|
|
|
605
785
|
const lower = input.toLowerCase();
|
|
606
786
|
const explicitPlanning =
|
|
607
787
|
/(\/plan\b|plan first|make a plan|implementation plan|先做计划|先出方案|先规划|先计划)/i.test(lower);
|
|
608
|
-
if (explicitPlanning) return
|
|
788
|
+
if (explicitPlanning) return 'complex';
|
|
609
789
|
|
|
610
790
|
const simpleSkip =
|
|
611
791
|
/(typo|readme|console\.log|log this|rename\s+\w+|one line|small tweak|tiny fix|格式化|拼写|注释|文案|小改|微调)/i.test(
|
|
612
792
|
lower
|
|
613
793
|
);
|
|
614
|
-
if (simpleSkip) return
|
|
794
|
+
if (simpleSkip) return 'simple';
|
|
615
795
|
|
|
616
796
|
const discussionFirst =
|
|
617
797
|
/(brainstorm|头脑风暴|方案|思路|怎么做|如何做|which (?:approach|option|way)|best way|trade-?off|not sure|unsure|unclear|whether it should|要不要|不确定|先别写|先不要写|先讨论|先想一下)/i.test(
|
|
618
798
|
lower
|
|
619
799
|
);
|
|
620
|
-
if (discussionFirst) return
|
|
800
|
+
if (discussionFirst) return 'simple';
|
|
621
801
|
|
|
622
802
|
const implementationRequest =
|
|
623
803
|
/\b(add|build|create|implement|support|introduce|design|refactor|rework|migrate|change|update|rewrite|restructure)\b/i.test(
|
|
624
804
|
lower
|
|
625
805
|
) ||
|
|
626
806
|
/(新增|增加|实现|支持|设计|重构|改造|迁移|调整|重写|重做)/i.test(lower);
|
|
627
|
-
if (!implementationRequest) return
|
|
628
|
-
|
|
629
|
-
const nonTrivialSignals =
|
|
630
|
-
/\b(auth|authentication|workflow|flow|system|architecture|api|endpoint|state management|cache|caching|database|migration|service|shared helper|helper module|refactor|multi[- ]file|across files|with tests?|and tests?|with validation|error handling)\b/i.test(
|
|
631
|
-
lower
|
|
632
|
-
) ||
|
|
633
|
-
/(架构|流程|系统|接口|缓存|数据库|迁移|服务|共享|模块|跨文件|测试|校验|错误处理)/i.test(lower);
|
|
807
|
+
if (!implementationRequest) return 'simple';
|
|
634
808
|
|
|
809
|
+
const broadSignalPattern =
|
|
810
|
+
/\b(auth|authentication|workflow|flow|system|architecture|api|endpoint|state management|session state|cache|caching|database|migration|service|integration|error handling|error recovery|shared helper|helper module)\b/gi;
|
|
811
|
+
const broadSignals = lower.match(broadSignalPattern) || [];
|
|
635
812
|
const multipleActions = /\b(and|plus|also|while|along with)\b/i.test(lower) || /[,、;;].+/.test(input);
|
|
636
813
|
const singleFileScoped =
|
|
637
814
|
/\b(?:in|inside|within|only in)\s+[-_/.\w]+\.(?:[cm]?[jt]sx?|py|go|rb|java|rs|php|md)\b/i.test(lower) ||
|
|
638
815
|
/\b(?:src|app|lib|tests?)\/[-_/.\w]+\.(?:[cm]?[jt]sx?|py|go|rb|java|rs|php|md)\b/i.test(lower);
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
816
|
+
const fileMentions = (lower.match(/[-_/.\w]+\.(?:[cm]?[jt]sx?|py|go|rb|java|rs|php|md)\b/g) || []).length;
|
|
817
|
+
const multiFileScope =
|
|
818
|
+
fileMentions >= 2 ||
|
|
819
|
+
/\b(across|multiple files?|cross-file|cross file)\b/i.test(lower) ||
|
|
820
|
+
/跨文件|多文件/.test(input);
|
|
821
|
+
const verificationHeavy = /\b(with tests?|and tests?|verify|validation|error handling|error recovery)\b/i.test(lower) || /测试|验证|校验|错误处理|错误恢复/.test(input);
|
|
822
|
+
const architectureHeavy =
|
|
823
|
+
broadSignals.length >= 3 ||
|
|
824
|
+
/\b(architecture|workflow|migration|state management|session state|integration)\b/i.test(lower) ||
|
|
825
|
+
/架构|流程|迁移|状态/.test(input);
|
|
826
|
+
|
|
827
|
+
if (singleFileScoped && !multipleActions && !verificationHeavy) return 'simple';
|
|
828
|
+
if (architectureHeavy && (multiFileScope || multipleActions || verificationHeavy)) return 'complex';
|
|
829
|
+
if (multiFileScope || verificationHeavy || multipleActions) return 'medium';
|
|
830
|
+
return 'simple';
|
|
644
831
|
}
|
|
645
832
|
|
|
646
833
|
function classifyAutoRoute(text = '') {
|
|
@@ -650,25 +837,42 @@ function classifyAutoRoute(text = '') {
|
|
|
650
837
|
return {
|
|
651
838
|
mode: 'brainstorm',
|
|
652
839
|
autoPlan: false,
|
|
653
|
-
selectedSkills
|
|
840
|
+
selectedSkills,
|
|
841
|
+
complexity: 'discussion'
|
|
654
842
|
};
|
|
655
843
|
}
|
|
656
844
|
|
|
657
|
-
|
|
845
|
+
const complexity = classifyTaskComplexity(text);
|
|
846
|
+
if (complexity === 'complex') {
|
|
658
847
|
return {
|
|
659
848
|
mode: 'auto_plan',
|
|
660
849
|
autoPlan: true,
|
|
661
|
-
selectedSkills: ['superpowers-lite']
|
|
850
|
+
selectedSkills: ['superpowers-lite'],
|
|
851
|
+
complexity
|
|
662
852
|
};
|
|
663
853
|
}
|
|
664
854
|
|
|
665
855
|
return {
|
|
666
|
-
mode: 'direct',
|
|
856
|
+
mode: complexity === 'medium' ? 'direct_medium' : 'direct',
|
|
667
857
|
autoPlan: false,
|
|
668
|
-
selectedSkills
|
|
858
|
+
selectedSkills,
|
|
859
|
+
complexity
|
|
669
860
|
};
|
|
670
861
|
}
|
|
671
862
|
|
|
863
|
+
function buildMediumTaskSystemPrompt(systemPrompt) {
|
|
864
|
+
const guidance = [
|
|
865
|
+
'Task Mode: medium',
|
|
866
|
+
'Execution guidance:',
|
|
867
|
+
'- Give a brief execution outline before coding.',
|
|
868
|
+
'- Keep the outline concise and focused on touched files/behaviors.',
|
|
869
|
+
'- Then implement directly instead of entering pending plan approval.',
|
|
870
|
+
'- Verify the changed behavior before finishing.',
|
|
871
|
+
'- If major ambiguity appears mid-task, say so clearly and ask for a plan instead of guessing.'
|
|
872
|
+
].join('\n');
|
|
873
|
+
return `${systemPrompt}\n\n${guidance}`;
|
|
874
|
+
}
|
|
875
|
+
|
|
672
876
|
function buildAutoSkillSystemPrompt(baseSystemPrompt, commands, config, text) {
|
|
673
877
|
const selected = classifyAutoRoute(text).selectedSkills.filter((name) => isSkillEnabled(config, name));
|
|
674
878
|
if (selected.length === 0) return baseSystemPrompt;
|
|
@@ -809,6 +1013,7 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
809
1013
|
const source = Array.isArray(plan?.steps) ? plan.steps : [];
|
|
810
1014
|
const requirements = deriveGoalRequirements(goal);
|
|
811
1015
|
const lightweightGoal = isLightweightAutoPlanGoal(goal, requirements);
|
|
1016
|
+
const taskClass = classifyPlanTaskClass(goal);
|
|
812
1017
|
const implementationSteps = source.filter((step) => step.role !== 'reviewer' && step.role !== 'tester');
|
|
813
1018
|
const primaryImplementationStep =
|
|
814
1019
|
implementationSteps.find((step) => step.role === 'coder') ||
|
|
@@ -827,17 +1032,31 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
827
1032
|
role: 'tester',
|
|
828
1033
|
task: `Test and verify the completed work for: ${goal}. Start with the artifacts produced by earlier implementation steps, run the most relevant checks available, report concrete evidence, and call out anything still unverified.`
|
|
829
1034
|
};
|
|
1035
|
+
const hasReviewer = source.some((step) => step.role === 'reviewer');
|
|
1036
|
+
const hasTester = source.some((step) => step.role === 'tester');
|
|
1037
|
+
|
|
1038
|
+
if (taskClass === 'advisory') {
|
|
1039
|
+
const advisorySteps = source.filter((step) => step.role === 'planner' || step.role === 'coder');
|
|
1040
|
+
return {
|
|
1041
|
+
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
1042
|
+
steps: advisorySteps.length > 0 ? advisorySteps.slice(0, 6) : [primaryImplementationStep]
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
830
1045
|
|
|
831
1046
|
if (lightweightGoal) {
|
|
832
1047
|
return {
|
|
833
1048
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
834
|
-
steps: [primaryImplementationStep, testerStep]
|
|
1049
|
+
steps: hasTester ? [primaryImplementationStep, testerStep] : [primaryImplementationStep]
|
|
835
1050
|
};
|
|
836
1051
|
}
|
|
837
1052
|
|
|
838
1053
|
return {
|
|
839
1054
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
840
|
-
steps: [
|
|
1055
|
+
steps: [
|
|
1056
|
+
...implementationSteps.slice(0, 6),
|
|
1057
|
+
...(hasReviewer ? [reviewerStep] : []),
|
|
1058
|
+
...(testerStep ? [testerStep] : [])
|
|
1059
|
+
]
|
|
841
1060
|
};
|
|
842
1061
|
}
|
|
843
1062
|
|
|
@@ -906,19 +1125,23 @@ function extractAcceptanceStatusItems(text = '') {
|
|
|
906
1125
|
}
|
|
907
1126
|
|
|
908
1127
|
function buildAutoPlanSystemSummary(auto) {
|
|
909
|
-
const
|
|
1128
|
+
const baseStatusTitle =
|
|
910
1129
|
auto.failedCount > 0 ? 'Auto plan finished with failures' : auto.warningCount > 0 ? 'Auto plan finished with warnings' : 'Auto plan finished';
|
|
1130
|
+
const statusTitle =
|
|
1131
|
+
auto.approvalStatus === 'pending' ? `${baseStatusTitle} (waiting for /plan approve)` : baseStatusTitle;
|
|
911
1132
|
const lines = [
|
|
912
1133
|
statusTitle,
|
|
913
|
-
`File: ${auto.filePath}`,
|
|
1134
|
+
`Plan File: ${auto.filePath}`,
|
|
914
1135
|
`Plan Summary: ${auto.summary || '-'}`,
|
|
915
1136
|
`Final Summary: ${auto.finalSummary || auto.summary || '-'}`,
|
|
916
|
-
`Approval: ${auto.approvalStatus || 'not_required'}
|
|
917
|
-
`Steps: ${auto.steps.length} total`,
|
|
918
|
-
`Completed: ${auto.completedCount}`,
|
|
919
|
-
`Warnings: ${auto.warningCount}`,
|
|
920
|
-
`Failed: ${auto.failedCount}`
|
|
1137
|
+
`Approval: ${auto.approvalStatus || 'not_required'}`
|
|
921
1138
|
];
|
|
1139
|
+
if (auto.approvalStatus !== 'pending') {
|
|
1140
|
+
lines.push(`Steps: ${auto.steps.length} total`);
|
|
1141
|
+
lines.push(`Completed: ${auto.completedCount}`);
|
|
1142
|
+
lines.push(`Warnings: ${auto.warningCount}`);
|
|
1143
|
+
lines.push(`Failed: ${auto.failedCount}`);
|
|
1144
|
+
}
|
|
922
1145
|
if (auto.warningTitles?.length) {
|
|
923
1146
|
lines.push(`Warning steps: ${auto.warningTitles.slice(0, 5).join(', ')}`);
|
|
924
1147
|
}
|
|
@@ -926,7 +1149,7 @@ function buildAutoPlanSystemSummary(auto) {
|
|
|
926
1149
|
lines.push(`Failed steps: ${auto.failedTitles.slice(0, 5).join(', ')}`);
|
|
927
1150
|
}
|
|
928
1151
|
if (auto.approvalStatus === 'pending') {
|
|
929
|
-
lines.push('Next: review the plan summary, then use /plan approve to start implementation or /plan stay to keep planning.');
|
|
1152
|
+
lines.push('Next: review the plan summary, then use /plan approve to start implementation, /plan auto run <goal> to plan and run in one step next time, or /plan stay to keep planning.');
|
|
930
1153
|
}
|
|
931
1154
|
return lines.join('\n');
|
|
932
1155
|
}
|
|
@@ -1002,6 +1225,7 @@ async function buildAutoPlanFinalSummary({
|
|
|
1002
1225
|
|
|
1003
1226
|
try {
|
|
1004
1227
|
const result = await createChatCompletion({
|
|
1228
|
+
sdkProvider: config.sdk?.provider,
|
|
1005
1229
|
baseUrl: config.gateway.base_url,
|
|
1006
1230
|
apiKey: config.gateway.api_key,
|
|
1007
1231
|
model: model || config.model.name,
|
|
@@ -1102,6 +1326,7 @@ async function buildSpecWithModel({
|
|
|
1102
1326
|
].join('\n');
|
|
1103
1327
|
|
|
1104
1328
|
const result = await createChatCompletion({
|
|
1329
|
+
sdkProvider: config.sdk?.provider,
|
|
1105
1330
|
baseUrl: config.gateway.base_url,
|
|
1106
1331
|
apiKey: config.gateway.api_key,
|
|
1107
1332
|
model: model || config.model.name,
|
|
@@ -1161,6 +1386,7 @@ async function buildPlanFromSpecWithModel({
|
|
|
1161
1386
|
].join('\n');
|
|
1162
1387
|
|
|
1163
1388
|
const result = await createChatCompletion({
|
|
1389
|
+
sdkProvider: config.sdk?.provider,
|
|
1164
1390
|
baseUrl: config.gateway.base_url,
|
|
1165
1391
|
apiKey: config.gateway.api_key,
|
|
1166
1392
|
model: model || config.model.name,
|
|
@@ -1276,6 +1502,7 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
1276
1502
|
const snapshot = {
|
|
1277
1503
|
sessionId: currentSession?.id || '',
|
|
1278
1504
|
mode: executionMode || config.execution?.mode || 'auto',
|
|
1505
|
+
sdkProvider: config.sdk?.provider || 'openai-compatible',
|
|
1279
1506
|
model: model || config.model?.name || '',
|
|
1280
1507
|
maxContextTokens
|
|
1281
1508
|
};
|
|
@@ -1336,7 +1563,7 @@ function buildPendingPlanApprovalMessage(planState) {
|
|
|
1336
1563
|
const lines = [
|
|
1337
1564
|
'Plan approval is still pending.',
|
|
1338
1565
|
`Goal: ${planState?.goal || '-'}`,
|
|
1339
|
-
`Plan
|
|
1566
|
+
`Plan File: ${planState?.filePath || '-'}`,
|
|
1340
1567
|
`Summary: ${planState?.finalSummary || planState?.summary || '-'}`,
|
|
1341
1568
|
'Use /plan approve to start implementation, or /plan stay to keep refining the plan first.'
|
|
1342
1569
|
];
|
|
@@ -1344,6 +1571,7 @@ function buildPendingPlanApprovalMessage(planState) {
|
|
|
1344
1571
|
}
|
|
1345
1572
|
|
|
1346
1573
|
function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
|
|
1574
|
+
const requirementPacket = buildGoalRequirementPacket(planState?.goal || '', 'coder');
|
|
1347
1575
|
const lines = [
|
|
1348
1576
|
'Approved implementation plan:',
|
|
1349
1577
|
`Original goal: ${planState?.goal || '-'}`,
|
|
@@ -1351,6 +1579,7 @@ function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
|
|
|
1351
1579
|
`Plan summary: ${planState?.summary || '-'}`,
|
|
1352
1580
|
`Final planning summary: ${planState?.finalSummary || planState?.summary || '-'}`,
|
|
1353
1581
|
`User approval: ${String(approvalText || '').trim() || 'approved'}`,
|
|
1582
|
+
requirementPacket,
|
|
1354
1583
|
Array.isArray(planState?.steps) && planState.steps.length > 0 ? 'Planned steps:' : '',
|
|
1355
1584
|
...(Array.isArray(planState?.steps)
|
|
1356
1585
|
? planState.steps.slice(0, 8).map((step, index) => `${index + 1}. [${step.role}] ${step.title} :: ${step.task}`)
|
|
@@ -1518,14 +1747,19 @@ async function askModel({
|
|
|
1518
1747
|
|
|
1519
1748
|
const projectContextSnippet = await buildProjectContextSnippet(process.cwd(), text).catch(() => '');
|
|
1520
1749
|
const effectiveSystemPrompt = projectContextSnippet
|
|
1521
|
-
? `${systemPrompt}\n\n${projectContextSnippet}\n\nUse this project context as lightweight guidance
|
|
1750
|
+
? `${systemPrompt}\n\n${projectContextSnippet}\n\nUse this project context as lightweight guidance and verify important details with fresh reads when needed.`
|
|
1522
1751
|
: systemPrompt;
|
|
1523
1752
|
|
|
1524
1753
|
const { definitions, handlers, formatters, deferredDefinitions } = getBuiltinTools({
|
|
1525
1754
|
workspaceRoot: process.cwd(),
|
|
1526
1755
|
config,
|
|
1527
1756
|
sessionId: session.id,
|
|
1528
|
-
onSystemEvent: onAgentEvent
|
|
1757
|
+
onSystemEvent: onAgentEvent,
|
|
1758
|
+
getTodos: () => normalizeTodos(session.todos),
|
|
1759
|
+
onTodosUpdate: (todos) => {
|
|
1760
|
+
session.todos = normalizeTodos(todos);
|
|
1761
|
+
scheduleSessionSave();
|
|
1762
|
+
}
|
|
1529
1763
|
});
|
|
1530
1764
|
|
|
1531
1765
|
let activeAssistantIndex = -1;
|
|
@@ -1595,6 +1829,7 @@ async function askModel({
|
|
|
1595
1829
|
};
|
|
1596
1830
|
|
|
1597
1831
|
const result = await createChatCompletionStream({
|
|
1832
|
+
sdkProvider: config.sdk?.provider,
|
|
1598
1833
|
baseUrl: config.gateway.base_url,
|
|
1599
1834
|
apiKey: config.gateway.api_key,
|
|
1600
1835
|
model: selectedModel,
|
|
@@ -1719,12 +1954,26 @@ async function buildAutoPlanAndRun({
|
|
|
1719
1954
|
model,
|
|
1720
1955
|
systemPrompt,
|
|
1721
1956
|
onAgentEvent,
|
|
1722
|
-
sessionId
|
|
1957
|
+
sessionId,
|
|
1958
|
+
taskClass
|
|
1723
1959
|
}) {
|
|
1960
|
+
const normalizedTaskClass = taskClass || classifyPlanTaskClass(goal);
|
|
1724
1961
|
const requirementPacket = buildGoalRequirementPacket(goal, 'planner');
|
|
1725
1962
|
const plannerPrompt = [
|
|
1726
1963
|
buildAutoPlanPlannerGuidance(),
|
|
1727
|
-
'
|
|
1964
|
+
'Planning policy:',
|
|
1965
|
+
'- First classify the user goal as one of: advisory, implementation, or verification-heavy.',
|
|
1966
|
+
'- advisory = analysis, review, audit, optimization suggestions, architecture feedback, brainstorming, planning, or recommendation requests.',
|
|
1967
|
+
'- implementation = add/build/create/implement/refactor/fix/update/change behavior in code or files.',
|
|
1968
|
+
'- verification-heavy = the user explicitly asks to run tests, verify findings, reproduce a bug, prove a claim, or validate a result.',
|
|
1969
|
+
'- For advisory goals, prefer only planner and coder roles. Do not use reviewer or tester unless the user explicitly asks for verification or review as a separate deliverable.',
|
|
1970
|
+
'- For advisory goals, do not emit generic filler steps such as "Test and verify", "Review recommendations", or other template-only steps.',
|
|
1971
|
+
'- For implementation goals, reviewer and tester are optional support roles, not defaults. Only include them when they clearly add value.',
|
|
1972
|
+
'- Every step title must be concrete and tied to the goal. Avoid vague titles like "Initial analysis", "Review recommendations", or "Test and verify" unless the user explicitly requested those activities.',
|
|
1973
|
+
'- If the task is purely to inspect the current project and suggest improvements, a lean 2-step or 3-step plan is preferred.',
|
|
1974
|
+
'- Example advisory roles: planner -> inspect project shape, coder -> synthesize findings and prioritized recommendations.',
|
|
1975
|
+
'- Example implementation roles: planner -> inspect target area, coder -> implement change, tester -> verify changed behavior.',
|
|
1976
|
+
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester","task":"..."}]}. No markdown.'
|
|
1728
1977
|
].join('\n');
|
|
1729
1978
|
let autoPlan = {
|
|
1730
1979
|
summary: `Auto plan for: ${goal}`,
|
|
@@ -1739,6 +1988,7 @@ async function buildAutoPlanAndRun({
|
|
|
1739
1988
|
let planningError = '';
|
|
1740
1989
|
try {
|
|
1741
1990
|
const planning = await createChatCompletion({
|
|
1991
|
+
sdkProvider: config.sdk?.provider,
|
|
1742
1992
|
baseUrl: config.gateway.base_url,
|
|
1743
1993
|
apiKey: config.gateway.api_key,
|
|
1744
1994
|
model: model || config.model.name,
|
|
@@ -1749,10 +1999,15 @@ async function buildAutoPlanAndRun({
|
|
|
1749
1999
|
content: [
|
|
1750
2000
|
'Create an execution plan and assign best sub-agent role for each step.',
|
|
1751
2001
|
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester","task":"..."}]}. No markdown.',
|
|
1752
|
-
'
|
|
2002
|
+
'The available roles are planner, coder, reviewer, and tester. Use only the roles the task actually needs.',
|
|
2003
|
+
`Task class: ${normalizedTaskClass}`,
|
|
2004
|
+
'Before choosing roles, decide whether the request is advisory, implementation, or verification-heavy.',
|
|
1753
2005
|
requirementPacket,
|
|
1754
2006
|
'The first step should usually inspect or clarify the target area before implementation.',
|
|
1755
|
-
'
|
|
2007
|
+
'For analysis, recommendation, optimization, audit, or project-review goals, keep the plan lean and usually limit it to planner/coder.',
|
|
2008
|
+
'Do not include reviewer/tester for advisory goals unless the user explicitly asks to validate, verify, or independently review the findings.',
|
|
2009
|
+
'Avoid template-only titles like "Initial analysis", "Review recommendations", or "Test and verify" for advisory goals.',
|
|
2010
|
+
'For implementation-heavy changes, prefer review and/or testing steps near the end only when they materially improve confidence.',
|
|
1756
2011
|
'Prefer 3-5 steps total.'
|
|
1757
2012
|
]
|
|
1758
2013
|
.filter(Boolean)
|
|
@@ -1866,6 +2121,17 @@ export async function createChatRuntime({
|
|
|
1866
2121
|
summary: initialIndex.summary
|
|
1867
2122
|
});
|
|
1868
2123
|
}
|
|
2124
|
+
const initialTodos = normalizeTodos(session?.todos);
|
|
2125
|
+
if (initialTodos.length > 0) {
|
|
2126
|
+
startupEvents.push({
|
|
2127
|
+
type: 'tool',
|
|
2128
|
+
id: `startup-todos-${String(session?.id || 'session')}`,
|
|
2129
|
+
name: 'update_todos',
|
|
2130
|
+
status: 'done',
|
|
2131
|
+
arguments: { todos: initialTodos },
|
|
2132
|
+
summary: `${initialTodos.length} todo item(s)`
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
1869
2135
|
let currentSession = session;
|
|
1870
2136
|
let config = initialConfig;
|
|
1871
2137
|
const baseSystemPrompt = systemPrompt;
|
|
@@ -1891,14 +2157,43 @@ export async function createChatRuntime({
|
|
|
1891
2157
|
messageCount: Array.isArray(currentSession.messages) ? currentSession.messages.length : 0
|
|
1892
2158
|
}
|
|
1893
2159
|
];
|
|
2160
|
+
try {
|
|
2161
|
+
const initialSessions = await listSessions(100);
|
|
2162
|
+
if (initialSessions.length > 0) {
|
|
2163
|
+
const merged = [
|
|
2164
|
+
{
|
|
2165
|
+
id: currentSession.id,
|
|
2166
|
+
messageCount: Array.isArray(currentSession.messages) ? currentSession.messages.length : 0
|
|
2167
|
+
},
|
|
2168
|
+
...initialSessions.map((session) => ({
|
|
2169
|
+
id: session.id,
|
|
2170
|
+
messageCount: Number(session.messageCount || 0)
|
|
2171
|
+
}))
|
|
2172
|
+
];
|
|
2173
|
+
const deduped = [];
|
|
2174
|
+
const seen = new Set();
|
|
2175
|
+
for (const session of merged) {
|
|
2176
|
+
const id = String(session.id || '').trim();
|
|
2177
|
+
if (!id || seen.has(id)) continue;
|
|
2178
|
+
seen.add(id);
|
|
2179
|
+
deduped.push(session);
|
|
2180
|
+
}
|
|
2181
|
+
historySessionCache = deduped;
|
|
2182
|
+
historyIdCache = deduped.map((session) => session.id);
|
|
2183
|
+
}
|
|
2184
|
+
} catch {
|
|
2185
|
+
// keep startup resilient even if historical sessions cannot be listed
|
|
2186
|
+
}
|
|
1894
2187
|
|
|
1895
2188
|
const configKeyHints = [
|
|
1896
2189
|
'gateway.base_url',
|
|
1897
2190
|
'gateway.api_key',
|
|
1898
2191
|
'model.name',
|
|
2192
|
+
'ui.language',
|
|
1899
2193
|
'ui.reply_language',
|
|
1900
2194
|
'execution.mode',
|
|
1901
2195
|
'shell.default',
|
|
2196
|
+
'sdk.provider',
|
|
1902
2197
|
'gateway.timeout_ms',
|
|
1903
2198
|
'gateway.max_retries',
|
|
1904
2199
|
'model.max_context_tokens',
|
|
@@ -1923,9 +2218,9 @@ export async function createChatRuntime({
|
|
|
1923
2218
|
'/help',
|
|
1924
2219
|
'/status',
|
|
1925
2220
|
'/config',
|
|
2221
|
+
'/memory',
|
|
1926
2222
|
'/mode',
|
|
1927
2223
|
'/plan',
|
|
1928
|
-
'/tasks',
|
|
1929
2224
|
'/history',
|
|
1930
2225
|
'/checkpoint',
|
|
1931
2226
|
'/agents',
|
|
@@ -1934,30 +2229,25 @@ export async function createChatRuntime({
|
|
|
1934
2229
|
'/retry'
|
|
1935
2230
|
];
|
|
1936
2231
|
const configSubcommandPriority = ['/config set', '/config get', '/config list', '/config reset'];
|
|
1937
|
-
const configSubcommandDescriptions = {
|
|
1938
|
-
'/config set': 'update a config value',
|
|
1939
|
-
'/config get': 'show a config value',
|
|
1940
|
-
'/config list': 'print the full config',
|
|
1941
|
-
'/config reset': 'reset config to defaults'
|
|
1942
|
-
};
|
|
1943
2232
|
|
|
1944
2233
|
const listCommandNames = () => {
|
|
2234
|
+
const completionCopy = getCompletionCopy(config.ui?.language);
|
|
1945
2235
|
const builtins = [
|
|
1946
|
-
{ name: 'help', description:
|
|
1947
|
-
{ name: 'exit', description:
|
|
1948
|
-
{ name: 'commands', description:
|
|
1949
|
-
{ name: 'status', description:
|
|
1950
|
-
{ name: 'mode', description:
|
|
1951
|
-
{ name: 'compact', description:
|
|
1952
|
-
{ name: '
|
|
1953
|
-
{ name: '
|
|
1954
|
-
{ name: '
|
|
1955
|
-
{ name: '
|
|
1956
|
-
{ name: '
|
|
1957
|
-
{ name: '
|
|
1958
|
-
{ name: 'history', description:
|
|
1959
|
-
{ name: 'debug', description:
|
|
1960
|
-
{ name: 'retry', description:
|
|
2236
|
+
{ name: 'help', description: completionCopy.commands.help },
|
|
2237
|
+
{ name: 'exit', description: completionCopy.commands.exit },
|
|
2238
|
+
{ name: 'commands', description: completionCopy.commands.commands },
|
|
2239
|
+
{ name: 'status', description: completionCopy.commands.status },
|
|
2240
|
+
{ name: 'mode', description: completionCopy.commands.mode },
|
|
2241
|
+
{ name: 'compact', description: completionCopy.commands.compact },
|
|
2242
|
+
{ name: 'checkpoint', description: completionCopy.commands.checkpoint },
|
|
2243
|
+
{ name: 'spec', description: completionCopy.commands.spec },
|
|
2244
|
+
{ name: 'plan', description: completionCopy.commands.plan },
|
|
2245
|
+
{ name: 'agents', description: completionCopy.commands.agents },
|
|
2246
|
+
{ name: 'config', description: completionCopy.commands.config },
|
|
2247
|
+
{ name: 'memory', description: completionCopy.commands.memory },
|
|
2248
|
+
{ name: 'history', description: completionCopy.commands.history },
|
|
2249
|
+
{ name: 'debug', description: completionCopy.commands.debug },
|
|
2250
|
+
{ name: 'retry', description: completionCopy.commands.retry }
|
|
1961
2251
|
];
|
|
1962
2252
|
const out = [];
|
|
1963
2253
|
for (const cmd of commands.values()) {
|
|
@@ -1991,8 +2281,8 @@ export async function createChatRuntime({
|
|
|
1991
2281
|
];
|
|
1992
2282
|
|
|
1993
2283
|
const historyTemplates = ['/history list', '/history current', '/history resume <session_id>'];
|
|
2284
|
+
const memoryTemplates = ['/memory list <scope>', '/memory search <scope> <query>', '/memory forget <scope> <id>'];
|
|
1994
2285
|
const modeTemplates = ['/mode normal', '/mode auto', '/mode plan'];
|
|
1995
|
-
const taskTemplates = ['/tasks', '/tasks add <title>', '/tasks start <id>', '/tasks done <id>', '/tasks remove <id>', '/tasks clear'];
|
|
1996
2286
|
const checkpointTemplates = [
|
|
1997
2287
|
'/checkpoint create <name>',
|
|
1998
2288
|
'/checkpoint list',
|
|
@@ -2000,15 +2290,15 @@ export async function createChatRuntime({
|
|
|
2000
2290
|
'/checkpoint load <id>'
|
|
2001
2291
|
];
|
|
2002
2292
|
const specTemplates = ['/spec <topic>'];
|
|
2003
|
-
const planTemplates = ['/plan <goal>', '/plan auto <goal>', '/plan from-spec <spec-path?>'];
|
|
2293
|
+
const planTemplates = ['/plan <goal>', '/plan auto <goal>', '/plan auto run <goal>', '/plan approve', '/plan from-spec <spec-path?>'];
|
|
2004
2294
|
const agentTemplates = ['/agents list', '/agents run planner <task>', '/agents run coder <task>', '/agents run reviewer <task>', '/agents run tester <task>'];
|
|
2005
2295
|
const debugTemplates = ['/debug keys on', '/debug keys off', '/debug keys status'];
|
|
2006
2296
|
const compactTemplates = compactOptions.map((opt) => `/compact ${opt}`);
|
|
2007
2297
|
const slashTemplates = [
|
|
2008
2298
|
...configTemplates,
|
|
2299
|
+
...memoryTemplates,
|
|
2009
2300
|
...historyTemplates,
|
|
2010
2301
|
...modeTemplates,
|
|
2011
|
-
...taskTemplates,
|
|
2012
2302
|
...checkpointTemplates,
|
|
2013
2303
|
...specTemplates,
|
|
2014
2304
|
...planTemplates,
|
|
@@ -2041,6 +2331,9 @@ export async function createChatRuntime({
|
|
|
2041
2331
|
const getCompletionOptions = (rawInput) => {
|
|
2042
2332
|
const input = String(rawInput || '');
|
|
2043
2333
|
if (!input.startsWith('/')) return [];
|
|
2334
|
+
const completionCopy = getCompletionCopy(config.ui?.language);
|
|
2335
|
+
const configSubcommandDescriptions = completionCopy.configSubcommands;
|
|
2336
|
+
const planSubcommandDescriptions = completionCopy.planSubcommands || {};
|
|
2044
2337
|
|
|
2045
2338
|
const hasTrailingSpace = /\s$/.test(input);
|
|
2046
2339
|
const body = input.slice(1);
|
|
@@ -2048,9 +2341,9 @@ export async function createChatRuntime({
|
|
|
2048
2341
|
const commandPart = tokens[0] || '';
|
|
2049
2342
|
const commandHasSubcommands = new Set([
|
|
2050
2343
|
'config',
|
|
2344
|
+
'memory',
|
|
2051
2345
|
'compact',
|
|
2052
2346
|
'mode',
|
|
2053
|
-
'tasks',
|
|
2054
2347
|
'checkpoint',
|
|
2055
2348
|
'plan',
|
|
2056
2349
|
'agents',
|
|
@@ -2065,19 +2358,21 @@ export async function createChatRuntime({
|
|
|
2065
2358
|
registerSuggestion(`/${entry.name}`, entry.description || '');
|
|
2066
2359
|
}
|
|
2067
2360
|
for (const template of configTemplates) {
|
|
2068
|
-
registerSuggestion(template, configSubcommandDescriptions[template] ||
|
|
2069
|
-
}
|
|
2070
|
-
for (const template of
|
|
2071
|
-
for (const template of
|
|
2072
|
-
for (const template of
|
|
2073
|
-
for (const template of checkpointTemplates) registerSuggestion(template,
|
|
2074
|
-
for (const template of specTemplates) registerSuggestion(template,
|
|
2075
|
-
for (const template of planTemplates)
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
for (const template of
|
|
2079
|
-
|
|
2080
|
-
|
|
2361
|
+
registerSuggestion(template, configSubcommandDescriptions[template] || completionCopy.generic.configCommand);
|
|
2362
|
+
}
|
|
2363
|
+
for (const template of memoryTemplates) registerSuggestion(template, completionCopy.generic.memoryCommand);
|
|
2364
|
+
for (const template of historyTemplates) registerSuggestion(template, completionCopy.generic.historyCommand);
|
|
2365
|
+
for (const template of modeTemplates) registerSuggestion(template, completionCopy.generic.modeCommand);
|
|
2366
|
+
for (const template of checkpointTemplates) registerSuggestion(template, completionCopy.generic.checkpointCommand);
|
|
2367
|
+
for (const template of specTemplates) registerSuggestion(template, completionCopy.generic.specCommand);
|
|
2368
|
+
for (const template of planTemplates) {
|
|
2369
|
+
registerSuggestion(template, planSubcommandDescriptions[template] || completionCopy.generic.planCommand);
|
|
2370
|
+
}
|
|
2371
|
+
for (const template of agentTemplates) registerSuggestion(template, completionCopy.generic.agentCommand);
|
|
2372
|
+
for (const template of debugTemplates) registerSuggestion(template, completionCopy.generic.debugCommand);
|
|
2373
|
+
for (const template of compactTemplates) registerSuggestion(template, completionCopy.generic.compactCommand);
|
|
2374
|
+
registerSuggestion('/retry', completionCopy.generic.retryCommand);
|
|
2375
|
+
registerSuggestion('/status', completionCopy.generic.statusCommand);
|
|
2081
2376
|
|
|
2082
2377
|
if (!commandPart) {
|
|
2083
2378
|
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
@@ -2105,7 +2400,7 @@ export async function createChatRuntime({
|
|
|
2105
2400
|
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
2106
2401
|
['set', 'get', 'list', 'reset']
|
|
2107
2402
|
.filter((s) => s.startsWith(subcommand))
|
|
2108
|
-
.map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] ||
|
|
2403
|
+
.map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] || completionCopy.generic.configCommand).value),
|
|
2109
2404
|
configSubcommandPriority
|
|
2110
2405
|
));
|
|
2111
2406
|
}
|
|
@@ -2114,77 +2409,84 @@ export async function createChatRuntime({
|
|
|
2114
2409
|
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
2115
2410
|
return configKeyHints
|
|
2116
2411
|
.filter((k) => k.startsWith(keyPrefix))
|
|
2117
|
-
.map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get')));
|
|
2412
|
+
.map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get', config.ui?.language)));
|
|
2118
2413
|
}
|
|
2119
2414
|
if (subcommand === 'set') {
|
|
2120
2415
|
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
2121
2416
|
return configKeyHints
|
|
2122
2417
|
.filter((k) => k.startsWith(keyPrefix))
|
|
2123
|
-
.map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set')));
|
|
2418
|
+
.map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set', config.ui?.language)));
|
|
2124
2419
|
}
|
|
2125
2420
|
|
|
2126
2421
|
return materializeSuggestions(configTemplates);
|
|
2127
2422
|
}
|
|
2128
2423
|
|
|
2424
|
+
if (commandPart === 'memory') {
|
|
2425
|
+
const sub = tokens[1] || '';
|
|
2426
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2427
|
+
return ['list', 'search', 'forget']
|
|
2428
|
+
.filter((item) => item.startsWith(sub))
|
|
2429
|
+
.map((item) => registerSuggestion(`/memory ${item}`, completionCopy.generic.memoryCommand));
|
|
2430
|
+
}
|
|
2431
|
+
const scope = tokens[2] || '';
|
|
2432
|
+
if (['list', 'search', 'forget'].includes(sub) && (tokens.length === 2 || (tokens.length === 3 && !hasTrailingSpace))) {
|
|
2433
|
+
return ['user', 'global', 'project']
|
|
2434
|
+
.filter((item) => item.startsWith(scope))
|
|
2435
|
+
.map((item) => registerSuggestion(`/memory ${sub} ${item}${sub === 'list' ? '' : ' '}`, completionCopy.generic.memoryCommand));
|
|
2436
|
+
}
|
|
2437
|
+
return materializeSuggestions(memoryTemplates);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2129
2440
|
if (commandPart === 'compact') {
|
|
2130
2441
|
const joined = tokens.slice(1).join(' ');
|
|
2131
2442
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2132
2443
|
return compactOptions
|
|
2133
2444
|
.filter((opt) => opt.startsWith(joined) || joined === '')
|
|
2134
|
-
.map((opt) => registerSuggestion(`/compact ${opt}`,
|
|
2445
|
+
.map((opt) => registerSuggestion(`/compact ${opt}`, completionCopy.generic.compactCommand));
|
|
2135
2446
|
}
|
|
2136
2447
|
return compactOptions
|
|
2137
2448
|
.filter((opt) => opt.includes(joined) || joined === '')
|
|
2138
|
-
.map((opt) => registerSuggestion(`/compact ${opt}`,
|
|
2449
|
+
.map((opt) => registerSuggestion(`/compact ${opt}`, completionCopy.generic.compactCommand));
|
|
2139
2450
|
}
|
|
2140
2451
|
|
|
2141
2452
|
if (commandPart === 'retry') {
|
|
2142
|
-
return [registerSuggestion('/retry',
|
|
2453
|
+
return [registerSuggestion('/retry', completionCopy.generic.retryCommand)];
|
|
2143
2454
|
}
|
|
2144
2455
|
if (commandPart === 'status') {
|
|
2145
|
-
return [registerSuggestion('/status',
|
|
2456
|
+
return [registerSuggestion('/status', completionCopy.generic.statusCommand)];
|
|
2146
2457
|
}
|
|
2147
2458
|
if (commandPart === 'mode') {
|
|
2148
2459
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2149
2460
|
const sub = tokens[1] || '';
|
|
2150
2461
|
return ['normal', 'auto', 'plan']
|
|
2151
2462
|
.filter((m) => m.startsWith(sub))
|
|
2152
|
-
.map((m) => registerSuggestion(`/mode ${m}`,
|
|
2463
|
+
.map((m) => registerSuggestion(`/mode ${m}`, completionCopy.generic.modeCommand));
|
|
2153
2464
|
}
|
|
2154
2465
|
return materializeSuggestions(modeTemplates);
|
|
2155
2466
|
}
|
|
2156
|
-
if (commandPart === 'tasks') {
|
|
2157
|
-
if (tokens.length <= 2 && !hasTrailingSpace) {
|
|
2158
|
-
const sub = tokens[1] || '';
|
|
2159
|
-
return ['add', 'start', 'done', 'remove', 'rm', 'clear']
|
|
2160
|
-
.filter((s) => s.startsWith(sub))
|
|
2161
|
-
.map((s) => registerSuggestion(`/tasks ${s}`, 'task board command'));
|
|
2162
|
-
}
|
|
2163
|
-
return materializeSuggestions(taskTemplates);
|
|
2164
|
-
}
|
|
2165
2467
|
if (commandPart === 'checkpoint') {
|
|
2166
2468
|
if (tokens.length <= 2 && !hasTrailingSpace) {
|
|
2167
2469
|
const sub = tokens[1] || '';
|
|
2168
2470
|
if (sub === 'list') {
|
|
2169
2471
|
return ['--all']
|
|
2170
|
-
.map((v) => registerSuggestion(`/checkpoint list ${v}`,
|
|
2472
|
+
.map((v) => registerSuggestion(`/checkpoint list ${v}`, completionCopy.generic.checkpointCommand));
|
|
2171
2473
|
}
|
|
2172
2474
|
return ['create', 'list', 'load']
|
|
2173
2475
|
.filter((s) => s.startsWith(sub))
|
|
2174
|
-
.map((s) => registerSuggestion(`/checkpoint ${s}`,
|
|
2476
|
+
.map((s) => registerSuggestion(`/checkpoint ${s}`, completionCopy.generic.checkpointCommand));
|
|
2175
2477
|
}
|
|
2176
2478
|
if (tokens[1] === 'list') {
|
|
2177
2479
|
const hint = tokens[2] || '';
|
|
2178
2480
|
return ['--all']
|
|
2179
2481
|
.filter((v) => v.startsWith(hint))
|
|
2180
|
-
.map((v) => registerSuggestion(`/checkpoint list ${v}`,
|
|
2482
|
+
.map((v) => registerSuggestion(`/checkpoint list ${v}`, completionCopy.generic.checkpointCommand));
|
|
2181
2483
|
}
|
|
2182
2484
|
if (tokens[1] === 'load') {
|
|
2183
2485
|
if (tokens.length >= 3) {
|
|
2184
2486
|
const hint = tokens[3] || '';
|
|
2185
2487
|
return ['--all']
|
|
2186
2488
|
.filter((v) => v.startsWith(hint))
|
|
2187
|
-
.map((v) => registerSuggestion(`/checkpoint load ${tokens[2]} ${v}`,
|
|
2489
|
+
.map((v) => registerSuggestion(`/checkpoint load ${tokens[2]} ${v}`, completionCopy.generic.checkpointCommand));
|
|
2188
2490
|
}
|
|
2189
2491
|
}
|
|
2190
2492
|
return materializeSuggestions(checkpointTemplates);
|
|
@@ -2193,11 +2495,25 @@ export async function createChatRuntime({
|
|
|
2193
2495
|
return materializeSuggestions(specTemplates);
|
|
2194
2496
|
}
|
|
2195
2497
|
if (commandPart === 'plan') {
|
|
2498
|
+
if (tokens[1] === 'auto' && (tokens.length === 2 || (tokens.length === 3 && !hasTrailingSpace))) {
|
|
2499
|
+
const sub = tokens[2] || '';
|
|
2500
|
+
return ['run']
|
|
2501
|
+
.filter((s) => s.startsWith(sub))
|
|
2502
|
+
.map((s) => registerSuggestion(`/plan auto ${s} `, planSubcommandDescriptions[`/plan auto ${s} <goal>`] || completionCopy.generic.planCommand));
|
|
2503
|
+
}
|
|
2196
2504
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2197
2505
|
const sub = tokens[1] || '';
|
|
2198
|
-
return ['auto', 'from-spec']
|
|
2506
|
+
return ['auto', 'approve', 'from-spec']
|
|
2199
2507
|
.filter((s) => s.startsWith(sub))
|
|
2200
|
-
.map((s) =>
|
|
2508
|
+
.map((s) =>
|
|
2509
|
+
registerSuggestion(
|
|
2510
|
+
`/plan ${s}`,
|
|
2511
|
+
planSubcommandDescriptions[`/plan ${s}`] ||
|
|
2512
|
+
planSubcommandDescriptions[`/plan ${s} <goal>`] ||
|
|
2513
|
+
planSubcommandDescriptions[`/plan ${s} <spec-path?>`] ||
|
|
2514
|
+
completionCopy.generic.planCommand
|
|
2515
|
+
)
|
|
2516
|
+
);
|
|
2201
2517
|
}
|
|
2202
2518
|
return materializeSuggestions(planTemplates);
|
|
2203
2519
|
}
|
|
@@ -2206,17 +2522,17 @@ export async function createChatRuntime({
|
|
|
2206
2522
|
const sub = tokens[1] || '';
|
|
2207
2523
|
if (sub === 'run') {
|
|
2208
2524
|
return ['planner', 'coder', 'reviewer', 'tester']
|
|
2209
|
-
.map((r) => registerSuggestion(`/agents run ${r} `,
|
|
2525
|
+
.map((r) => registerSuggestion(`/agents run ${r} `, completionCopy.generic.agentCommand));
|
|
2210
2526
|
}
|
|
2211
2527
|
return ['list', 'run']
|
|
2212
2528
|
.filter((s) => s.startsWith(sub))
|
|
2213
|
-
.map((s) => registerSuggestion(`/agents ${s}`,
|
|
2529
|
+
.map((s) => registerSuggestion(`/agents ${s}`, completionCopy.generic.agentCommand));
|
|
2214
2530
|
}
|
|
2215
2531
|
if (tokens[1] === 'run') {
|
|
2216
2532
|
const rolePrefix = tokens[2] || '';
|
|
2217
2533
|
return ['planner', 'coder', 'reviewer', 'tester']
|
|
2218
2534
|
.filter((r) => r.startsWith(rolePrefix))
|
|
2219
|
-
.map((r) => registerSuggestion(`/agents run ${r} `,
|
|
2535
|
+
.map((r) => registerSuggestion(`/agents run ${r} `, completionCopy.generic.agentCommand));
|
|
2220
2536
|
}
|
|
2221
2537
|
return materializeSuggestions(agentTemplates);
|
|
2222
2538
|
}
|
|
@@ -2230,13 +2546,13 @@ export async function createChatRuntime({
|
|
|
2230
2546
|
.map((session) => ({
|
|
2231
2547
|
value: `/history resume ${session.id}`,
|
|
2232
2548
|
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
|
|
2233
|
-
description:
|
|
2549
|
+
description: completionCopy.generic.resumeSession
|
|
2234
2550
|
}));
|
|
2235
2551
|
if (dynamic.length > 0) return dynamic;
|
|
2236
2552
|
}
|
|
2237
2553
|
return ['list', 'current', 'resume']
|
|
2238
2554
|
.filter((s) => s.startsWith(sub))
|
|
2239
|
-
.map((s) => registerSuggestion(`/history ${s}`,
|
|
2555
|
+
.map((s) => registerSuggestion(`/history ${s}`, completionCopy.generic.historyCommand));
|
|
2240
2556
|
}
|
|
2241
2557
|
if (sub === 'resume') {
|
|
2242
2558
|
const idPrefix = tokens[2] || '';
|
|
@@ -2245,7 +2561,7 @@ export async function createChatRuntime({
|
|
|
2245
2561
|
.map((session) => ({
|
|
2246
2562
|
value: `/history resume ${session.id}`,
|
|
2247
2563
|
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
|
|
2248
|
-
description:
|
|
2564
|
+
description: completionCopy.generic.resumeSession
|
|
2249
2565
|
}));
|
|
2250
2566
|
if (dynamic.length > 0) return dynamic;
|
|
2251
2567
|
return materializeSuggestions(historyTemplates);
|
|
@@ -2258,17 +2574,17 @@ export async function createChatRuntime({
|
|
|
2258
2574
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2259
2575
|
if (sub === 'keys') {
|
|
2260
2576
|
return ['on', 'off', 'status']
|
|
2261
|
-
.map((v) => registerSuggestion(`/debug keys ${v}`,
|
|
2577
|
+
.map((v) => registerSuggestion(`/debug keys ${v}`, completionCopy.generic.keyboardDebugCommand));
|
|
2262
2578
|
}
|
|
2263
2579
|
return ['keys']
|
|
2264
2580
|
.filter((s) => s.startsWith(sub))
|
|
2265
|
-
.map((s) => registerSuggestion(`/debug ${s}`,
|
|
2581
|
+
.map((s) => registerSuggestion(`/debug ${s}`, completionCopy.generic.debugCommand));
|
|
2266
2582
|
}
|
|
2267
2583
|
if (sub === 'keys') {
|
|
2268
2584
|
const action = tokens[2] || '';
|
|
2269
2585
|
return ['on', 'off', 'status']
|
|
2270
2586
|
.filter((v) => v.startsWith(action))
|
|
2271
|
-
.map((v) => registerSuggestion(`/debug keys ${v}`,
|
|
2587
|
+
.map((v) => registerSuggestion(`/debug keys ${v}`, completionCopy.generic.keyboardDebugCommand));
|
|
2272
2588
|
}
|
|
2273
2589
|
return materializeSuggestions(debugTemplates);
|
|
2274
2590
|
}
|
|
@@ -2286,6 +2602,17 @@ export async function createChatRuntime({
|
|
|
2286
2602
|
await saveSession(currentSession);
|
|
2287
2603
|
};
|
|
2288
2604
|
|
|
2605
|
+
const buildActiveSystemPrompt = async () => {
|
|
2606
|
+
const soulPrompt = await buildSystemPromptWithSoul(baseSystemPrompt, config);
|
|
2607
|
+
const memorySnapshot = await buildMemorySnapshot({
|
|
2608
|
+
config,
|
|
2609
|
+
workspaceRoot: process.cwd()
|
|
2610
|
+
}).catch(() => '');
|
|
2611
|
+
const memoryGuide =
|
|
2612
|
+
'Persistent memory stores durable preferences and stable workflow knowledge. Verify changeable details from files, and only write memory for future-useful, non-sensitive facts.';
|
|
2613
|
+
return [soulPrompt, memorySnapshot, memoryGuide].filter(Boolean).join('\n\n');
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2289
2616
|
const isImmediateLocalInput = (line) => {
|
|
2290
2617
|
const parsedInput = parseInput(line);
|
|
2291
2618
|
if (parsedInput.type !== 'slash') return false;
|
|
@@ -2301,9 +2628,9 @@ export async function createChatRuntime({
|
|
|
2301
2628
|
'commands',
|
|
2302
2629
|
'status',
|
|
2303
2630
|
'mode',
|
|
2304
|
-
'tasks',
|
|
2305
2631
|
'checkpoint',
|
|
2306
2632
|
'history',
|
|
2633
|
+
'memory',
|
|
2307
2634
|
'config',
|
|
2308
2635
|
'compact',
|
|
2309
2636
|
'debug'
|
|
@@ -2312,7 +2639,7 @@ export async function createChatRuntime({
|
|
|
2312
2639
|
};
|
|
2313
2640
|
|
|
2314
2641
|
const submit = async (line, onAgentEvent) => {
|
|
2315
|
-
const activeReplySystemPrompt = await
|
|
2642
|
+
const activeReplySystemPrompt = await buildActiveSystemPrompt();
|
|
2316
2643
|
try {
|
|
2317
2644
|
await appendInputHistory(line);
|
|
2318
2645
|
} catch {
|
|
@@ -2331,14 +2658,14 @@ export async function createChatRuntime({
|
|
|
2331
2658
|
if (parsedInput.command === 'help') {
|
|
2332
2659
|
return {
|
|
2333
2660
|
type: 'system',
|
|
2334
|
-
text: 'Commands: /help /exit /commands /status /mode /compact /
|
|
2661
|
+
text: 'Commands: /help /exit /commands /status /mode /compact /checkpoint /spec /plan /agents /config /memory /history /debug /retry /<custom> !<shell>'
|
|
2335
2662
|
};
|
|
2336
2663
|
}
|
|
2337
2664
|
if (parsedInput.command === 'status') {
|
|
2338
|
-
const
|
|
2665
|
+
const todoCount = countActiveTodos(currentSession.todos);
|
|
2339
2666
|
return {
|
|
2340
2667
|
type: 'system',
|
|
2341
|
-
text: `mode=${executionMode} | model=${model || config.model.name} | max_ctx=${effectiveMaxContextTokens(config)} | session=${currentSession.id} |
|
|
2668
|
+
text: `mode=${executionMode} | model=${model || config.model.name} | max_ctx=${effectiveMaxContextTokens(config)} | session=${currentSession.id} | todos=${todoCount}`
|
|
2342
2669
|
};
|
|
2343
2670
|
}
|
|
2344
2671
|
if (parsedInput.command === 'mode') {
|
|
@@ -2356,74 +2683,15 @@ export async function createChatRuntime({
|
|
|
2356
2683
|
await persistLocalExchange(line, text);
|
|
2357
2684
|
return { type: 'system', text };
|
|
2358
2685
|
}
|
|
2359
|
-
if (parsedInput.command === 'tasks') {
|
|
2360
|
-
const sub = (parsedInput.args[0] || '').trim().toLowerCase();
|
|
2361
|
-
if (!sub) {
|
|
2362
|
-
const tasks = await loadTasks(process.cwd(), currentSession.id);
|
|
2363
|
-
if (tasks.length === 0) return { type: 'system', text: 'No tasks' };
|
|
2364
|
-
const rows = tasks.map((t, idx) => `${idx + 1}. ${t.id} | ${t.status} | ${t.title}`);
|
|
2365
|
-
return { type: 'system', text: rows.join('\n') };
|
|
2366
|
-
}
|
|
2367
|
-
if (sub === 'add') {
|
|
2368
|
-
const title = parsedInput.args.slice(1).join(' ').trim();
|
|
2369
|
-
if (!title) return { type: 'system', text: 'Usage: /tasks add <title>' };
|
|
2370
|
-
const created = await createTasks([{ title }], process.cwd(), currentSession.id);
|
|
2371
|
-
const text = `Created task: ${created[0]?.id || '-'} | ${title}`;
|
|
2372
|
-
await persistLocalExchange(line, text);
|
|
2373
|
-
return { type: 'system', text };
|
|
2374
|
-
}
|
|
2375
|
-
if (sub === 'start') {
|
|
2376
|
-
const id = parsedInput.args[1];
|
|
2377
|
-
if (!id) return { type: 'system', text: 'Usage: /tasks start <id>' };
|
|
2378
|
-
const updated = await updateTask(id, { status: 'in_progress' }, process.cwd(), currentSession.id);
|
|
2379
|
-
if (!updated) return { type: 'system', text: `Task not found: ${id}` };
|
|
2380
|
-
const text = `Task in progress: ${id}`;
|
|
2381
|
-
await persistLocalExchange(line, text);
|
|
2382
|
-
return { type: 'system', text };
|
|
2383
|
-
}
|
|
2384
|
-
if (sub === 'done') {
|
|
2385
|
-
const id = parsedInput.args[1];
|
|
2386
|
-
if (!id) return { type: 'system', text: 'Usage: /tasks done <id>' };
|
|
2387
|
-
const updated = await updateTask(id, { status: 'completed' }, process.cwd(), currentSession.id);
|
|
2388
|
-
if (!updated) return { type: 'system', text: `Task not found: ${id}` };
|
|
2389
|
-
const text = `Task completed: ${id}`;
|
|
2390
|
-
await persistLocalExchange(line, text);
|
|
2391
|
-
return { type: 'system', text };
|
|
2392
|
-
}
|
|
2393
|
-
if (sub === 'remove' || sub === 'rm') {
|
|
2394
|
-
const id = parsedInput.args[1];
|
|
2395
|
-
if (!id) return { type: 'system', text: 'Usage: /tasks remove <id>' };
|
|
2396
|
-
const result = await deleteTasks([id], process.cwd(), currentSession.id);
|
|
2397
|
-
const text = `Removed=${result.removed}, Remaining=${result.remaining}`;
|
|
2398
|
-
await persistLocalExchange(line, text);
|
|
2399
|
-
return { type: 'system', text };
|
|
2400
|
-
}
|
|
2401
|
-
if (sub === 'clear') {
|
|
2402
|
-
await clearTasks(process.cwd(), currentSession.id);
|
|
2403
|
-
const text = 'All tasks cleared';
|
|
2404
|
-
await persistLocalExchange(line, text);
|
|
2405
|
-
return { type: 'system', text };
|
|
2406
|
-
}
|
|
2407
|
-
// shorthand: /tasks implement x
|
|
2408
|
-
const title = parsedInput.args.join(' ').trim();
|
|
2409
|
-
if (title) {
|
|
2410
|
-
const created = await createTasks([{ title }], process.cwd(), currentSession.id);
|
|
2411
|
-
const text = `Created task: ${created[0]?.id || '-'} | ${title}`;
|
|
2412
|
-
await persistLocalExchange(line, text);
|
|
2413
|
-
return { type: 'system', text };
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
2686
|
if (parsedInput.command === 'checkpoint') {
|
|
2417
2687
|
const sub = (parsedInput.args[0] || 'list').trim().toLowerCase();
|
|
2418
2688
|
if (sub === 'create') {
|
|
2419
2689
|
const name = parsedInput.args.slice(1).join(' ').trim();
|
|
2420
|
-
const tasks = await loadTasks(process.cwd(), currentSession.id);
|
|
2421
2690
|
const cp = await createCheckpoint(
|
|
2422
2691
|
{
|
|
2423
2692
|
name,
|
|
2424
2693
|
session: currentSession,
|
|
2425
|
-
config
|
|
2426
|
-
tasks
|
|
2694
|
+
config
|
|
2427
2695
|
},
|
|
2428
2696
|
process.cwd()
|
|
2429
2697
|
);
|
|
@@ -2458,17 +2726,6 @@ export async function createChatRuntime({
|
|
|
2458
2726
|
config = cp.config;
|
|
2459
2727
|
executionMode = config.execution?.mode || executionMode;
|
|
2460
2728
|
}
|
|
2461
|
-
if (Array.isArray(cp?.tasks)) {
|
|
2462
|
-
await clearTasks(process.cwd(), currentSession.id);
|
|
2463
|
-
if (cp.tasks.length > 0) {
|
|
2464
|
-
// restore with new ids to avoid stale references
|
|
2465
|
-
await createTasks(
|
|
2466
|
-
cp.tasks.map((t) => ({ title: t.title, description: t.description })),
|
|
2467
|
-
process.cwd(),
|
|
2468
|
-
currentSession.id
|
|
2469
|
-
);
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
2729
|
const text = `Checkpoint loaded: ${id}`;
|
|
2473
2730
|
await persistLocalExchange(line, text, { includeUser: false });
|
|
2474
2731
|
return { type: 'system', text };
|
|
@@ -2505,8 +2762,9 @@ export async function createChatRuntime({
|
|
|
2505
2762
|
if (parsedInput.command === 'plan') {
|
|
2506
2763
|
const sub = (parsedInput.args[0] || '').trim().toLowerCase();
|
|
2507
2764
|
if (sub === 'auto') {
|
|
2508
|
-
const
|
|
2509
|
-
|
|
2765
|
+
const runImmediately = (parsedInput.args[1] || '').trim().toLowerCase() === 'run';
|
|
2766
|
+
const goal = parsedInput.args.slice(runImmediately ? 2 : 1).join(' ').trim();
|
|
2767
|
+
if (!goal) return { type: 'system', text: 'Usage: /plan auto <goal> | /plan auto run <goal>' };
|
|
2510
2768
|
const auto = await buildAutoPlanAndRun({
|
|
2511
2769
|
goal,
|
|
2512
2770
|
session: currentSession,
|
|
@@ -2514,8 +2772,35 @@ export async function createChatRuntime({
|
|
|
2514
2772
|
model,
|
|
2515
2773
|
systemPrompt: activeReplySystemPrompt,
|
|
2516
2774
|
onAgentEvent,
|
|
2517
|
-
sessionId: currentSession.id
|
|
2775
|
+
sessionId: currentSession.id,
|
|
2776
|
+
taskClass: classifyPlanTaskClass(goal)
|
|
2518
2777
|
});
|
|
2778
|
+
if (runImmediately) {
|
|
2779
|
+
const result = await askModel({
|
|
2780
|
+
text: buildApprovedPlanExecutionPrompt(
|
|
2781
|
+
{
|
|
2782
|
+
status: 'approved',
|
|
2783
|
+
source: 'auto',
|
|
2784
|
+
goal,
|
|
2785
|
+
filePath: auto.filePath,
|
|
2786
|
+
summary: auto.summary || '',
|
|
2787
|
+
finalSummary: auto.finalSummary || auto.summary || '',
|
|
2788
|
+
steps: Array.isArray(auto.steps) ? auto.steps : []
|
|
2789
|
+
},
|
|
2790
|
+
'/plan auto run'
|
|
2791
|
+
),
|
|
2792
|
+
session: currentSession,
|
|
2793
|
+
config,
|
|
2794
|
+
model,
|
|
2795
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2796
|
+
onAgentEvent,
|
|
2797
|
+
executionMode: 'auto'
|
|
2798
|
+
});
|
|
2799
|
+
currentSession.planState = null;
|
|
2800
|
+
executionMode = 'auto';
|
|
2801
|
+
await saveSession(currentSession);
|
|
2802
|
+
return { type: 'assistant', text: result.text };
|
|
2803
|
+
}
|
|
2519
2804
|
currentSession.planState = {
|
|
2520
2805
|
status: 'pending_approval',
|
|
2521
2806
|
source: 'auto',
|
|
@@ -2595,7 +2880,7 @@ export async function createChatRuntime({
|
|
|
2595
2880
|
}
|
|
2596
2881
|
|
|
2597
2882
|
const goal = parsedInput.args.join(' ').trim();
|
|
2598
|
-
if (!goal) return { type: 'system', text: 'Usage: /plan <goal> | /plan auto <goal> | /plan from-spec <spec-path?>' };
|
|
2883
|
+
if (!goal) return { type: 'system', text: 'Usage: /plan <goal> | /plan auto <goal> | /plan auto run <goal> | /plan from-spec <spec-path?>' };
|
|
2599
2884
|
const content = buildPlanTemplate(goal);
|
|
2600
2885
|
const filePath = await writeMarkdownInProjectDir(
|
|
2601
2886
|
'plans',
|
|
@@ -2690,11 +2975,55 @@ export async function createChatRuntime({
|
|
|
2690
2975
|
];
|
|
2691
2976
|
return {
|
|
2692
2977
|
type: 'system',
|
|
2693
|
-
text: `Switched to session: ${targetId} (${loaded.messages.length} messages)
|
|
2978
|
+
text: `Switched to session: ${targetId} (${loaded.messages.length} messages)`,
|
|
2979
|
+
restoredMessages: structuredClone(loaded.messages || [])
|
|
2694
2980
|
};
|
|
2695
2981
|
}
|
|
2696
2982
|
return { type: 'system', text: `Unknown /history subcommand: ${sub}` };
|
|
2697
2983
|
}
|
|
2984
|
+
if (parsedInput.command === 'memory') {
|
|
2985
|
+
const sub = String(parsedInput.args[0] || '').trim().toLowerCase();
|
|
2986
|
+
if (!sub) {
|
|
2987
|
+
return { type: 'system', text: 'Usage: /memory list <user|global|project> | /memory search <scope> <query> | /memory forget <scope> <id>' };
|
|
2988
|
+
}
|
|
2989
|
+
if (sub === 'list') {
|
|
2990
|
+
const scope = String(parsedInput.args[1] || '').trim().toLowerCase();
|
|
2991
|
+
if (!['user', 'global', 'project'].includes(scope)) {
|
|
2992
|
+
return { type: 'system', text: 'Usage: /memory list <user|global|project>' };
|
|
2993
|
+
}
|
|
2994
|
+
const items = await listMemories({ scope, workspaceRoot: process.cwd() });
|
|
2995
|
+
if (items.length === 0) return { type: 'system', text: `No ${scope} memories found.` };
|
|
2996
|
+
return {
|
|
2997
|
+
type: 'system',
|
|
2998
|
+
text: items.map((item) => `${item.id} | ${item.kind} | ${item.content}`).join('\n')
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
if (sub === 'search') {
|
|
3002
|
+
const scope = String(parsedInput.args[1] || '').trim().toLowerCase();
|
|
3003
|
+
const query = parsedInput.args.slice(2).join(' ').trim();
|
|
3004
|
+
if (!['user', 'global', 'project'].includes(scope) || !query) {
|
|
3005
|
+
return { type: 'system', text: 'Usage: /memory search <user|global|project> <query>' };
|
|
3006
|
+
}
|
|
3007
|
+
const items = await searchMemories({ scope, query, workspaceRoot: process.cwd() });
|
|
3008
|
+
if (items.length === 0) return { type: 'system', text: `No ${scope} memories matched: ${query}` };
|
|
3009
|
+
return {
|
|
3010
|
+
type: 'system',
|
|
3011
|
+
text: items.map((item) => `${item.id} | ${item.kind} | ${item.content}`).join('\n')
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
if (sub === 'forget') {
|
|
3015
|
+
const scope = String(parsedInput.args[1] || '').trim().toLowerCase();
|
|
3016
|
+
const id = String(parsedInput.args[2] || '').trim();
|
|
3017
|
+
if (!['user', 'global', 'project'].includes(scope) || !id) {
|
|
3018
|
+
return { type: 'system', text: 'Usage: /memory forget <user|global|project> <id>' };
|
|
3019
|
+
}
|
|
3020
|
+
const result = await forgetMemory({ scope, id, workspaceRoot: process.cwd() });
|
|
3021
|
+
const text = `Removed ${Number(result.removed || 0)} ${scope} memory item(s)`;
|
|
3022
|
+
await persistLocalExchange(line, text, { includeUser: false });
|
|
3023
|
+
return { type: 'system', text };
|
|
3024
|
+
}
|
|
3025
|
+
return { type: 'system', text: `Unknown /memory subcommand: ${sub}` };
|
|
3026
|
+
}
|
|
2698
3027
|
if (parsedInput.command === 'retry') {
|
|
2699
3028
|
const lastUser = [...currentSession.messages].reverse().find((m) => m.role === 'user');
|
|
2700
3029
|
if (!lastUser?.content) {
|
|
@@ -2925,7 +3254,8 @@ export async function createChatRuntime({
|
|
|
2925
3254
|
model,
|
|
2926
3255
|
systemPrompt: activeReplySystemPrompt,
|
|
2927
3256
|
onAgentEvent,
|
|
2928
|
-
sessionId: currentSession.id
|
|
3257
|
+
sessionId: currentSession.id,
|
|
3258
|
+
taskClass: classifyPlanTaskClass(expandedText)
|
|
2929
3259
|
});
|
|
2930
3260
|
currentSession.planState = {
|
|
2931
3261
|
status: 'pending_approval',
|
|
@@ -2949,7 +3279,11 @@ export async function createChatRuntime({
|
|
|
2949
3279
|
names: selectedAutoSkills
|
|
2950
3280
|
});
|
|
2951
3281
|
}
|
|
2952
|
-
const
|
|
3282
|
+
const skillPrompt = buildAutoSkillSystemPrompt(activeReplySystemPrompt, commands, config, expandedText);
|
|
3283
|
+
const routedSystemPrompt =
|
|
3284
|
+
autoRoute.mode === 'direct_medium'
|
|
3285
|
+
? buildMediumTaskSystemPrompt(skillPrompt)
|
|
3286
|
+
: skillPrompt;
|
|
2953
3287
|
const result = await askModel({
|
|
2954
3288
|
text: expandedText,
|
|
2955
3289
|
session: currentSession,
|