codemini-cli 0.3.0 → 0.3.2
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/package.json +2 -1
- package/souls/anime.md +13 -2
- package/souls/caveman.md +12 -2
- package/souls/ceo.md +14 -3
- package/souls/default.md +10 -2
- package/souls/pirate.md +13 -3
- package/souls/playful.md +13 -3
- package/souls/professional.md +13 -3
- package/src/cli.js +2 -1
- package/src/commands/chat.js +3 -0
- package/src/commands/run.js +6 -2
- package/src/core/agent-loop.js +13 -9
- package/src/core/ast.js +2 -55
- package/src/core/bounded-cache.js +121 -0
- package/src/core/chat-runtime.js +552 -143
- package/src/core/config-store.js +30 -0
- package/src/core/constants.js +171 -0
- package/src/core/crypto-utils.js +18 -0
- package/src/core/memory-policy.js +27 -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/project-index.js +6 -34
- package/src/core/provider/anthropic.js +388 -0
- package/src/core/provider/index.js +37 -0
- package/src/core/soul.js +3 -2
- package/src/core/tools.js +175 -71
- package/src/tui/chat-app.js +291 -38
- package/src/tui/tool-activity/presenters/command.js +14 -1
- package/src/tui/tool-activity/presenters/files.js +23 -1
- package/src/tui/tool-activity/presenters/system.js +1 -1
- package/src/tui/tool-narration/presenters/glob.js +2 -2
- package/src/tui/tool-narration/presenters/grep.js +2 -2
- package/src/tui/tool-narration/presenters/list.js +2 -2
- package/src/tui/tool-narration/presenters/run.js +2 -2
package/src/core/chat-runtime.js
CHANGED
|
@@ -6,7 +6,7 @@ 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';
|
|
@@ -30,6 +30,8 @@ import { buildSystemPromptWithReplyLanguage } from './reply-language.js';
|
|
|
30
30
|
import { buildSystemPromptWithSoul } from './soul.js';
|
|
31
31
|
import { getProjectPlansDir, getProjectSpecsDir, getProjectWorkspaceDir, getSessionsDir } from './paths.js';
|
|
32
32
|
import { buildProjectContextSnippet, initializeProjectIndex } from './project-index.js';
|
|
33
|
+
import { buildMemorySnapshot } from './memory-prompt.js';
|
|
34
|
+
import { forgetMemory, listMemories, searchMemories } from './memory-store.js';
|
|
33
35
|
|
|
34
36
|
function toOpenAIMessages(sessionMessages) {
|
|
35
37
|
const mapped = [];
|
|
@@ -74,35 +76,199 @@ function prioritizeByPreferredOrder(items, preferredOrder) {
|
|
|
74
76
|
});
|
|
75
77
|
}
|
|
76
78
|
|
|
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
|
-
|
|
79
|
+
function normalizeUiLocale(value) {
|
|
80
|
+
return String(value || '').toLowerCase().startsWith('en') ? 'en' : 'zh';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getCompletionCopy(language = 'zh') {
|
|
84
|
+
const lang = normalizeUiLocale(language);
|
|
85
|
+
return {
|
|
86
|
+
zh: {
|
|
87
|
+
configLabels: {
|
|
88
|
+
'gateway.base_url': '网关基础 URL',
|
|
89
|
+
'gateway.api_key': '网关 API Key',
|
|
90
|
+
'sdk.provider': 'SDK provider',
|
|
91
|
+
'gateway.timeout_ms': '网关超时时间(毫秒)',
|
|
92
|
+
'gateway.max_retries': '网关重试次数',
|
|
93
|
+
'model.name': '当前模型名称',
|
|
94
|
+
'model.max_context_tokens': '模型上下文 token 上限',
|
|
95
|
+
'ui.language': '界面语言',
|
|
96
|
+
'ui.reply_language': '回复语言',
|
|
97
|
+
'execution.mode': '执行模式',
|
|
98
|
+
'execution.always_allow_tools': '始终允许的工具列表',
|
|
99
|
+
'execution.max_steps': '最大工具步骤数',
|
|
100
|
+
'context.preflight_trigger_pct': '预压缩阈值',
|
|
101
|
+
'context.hard_limit_pct': '硬压缩阈值',
|
|
102
|
+
'context.tool_result_max_chars': '工具结果字符上限',
|
|
103
|
+
'context.read_file_default_lines': 'read_file 默认行数窗口',
|
|
104
|
+
'context.read_file_max_chars': 'read_file 字符上限',
|
|
105
|
+
'sessions.max_sessions': '会话保留上限',
|
|
106
|
+
'sessions.retention_days': '会话保留天数',
|
|
107
|
+
'shell.default': '默认 shell',
|
|
108
|
+
'shell.timeout_ms': 'shell 超时时间(毫秒)',
|
|
109
|
+
'context.max_tokens': '上下文 token 预算',
|
|
110
|
+
'soul.preset': 'soul 预设',
|
|
111
|
+
'soul.custom_path': '自定义 soul 路径',
|
|
112
|
+
'policy.safe_mode': '安全模式开关',
|
|
113
|
+
'policy.allow_dangerous_commands': '危险命令开关'
|
|
114
|
+
},
|
|
115
|
+
optionHints: {
|
|
116
|
+
'sdk.provider': '可选:openai-compatible | anthropic',
|
|
117
|
+
'ui.language': '可选:zh | en',
|
|
118
|
+
'ui.reply_language': '可选:zh | en',
|
|
119
|
+
'execution.mode': '可选:auto | normal | plan',
|
|
120
|
+
'shell.default': '常用:bash | powershell',
|
|
121
|
+
'policy.safe_mode': '可选:true | false',
|
|
122
|
+
'policy.allow_dangerous_commands': '可选:true | false'
|
|
123
|
+
},
|
|
124
|
+
describeSet: (label, hint) => `设置${label}${hint ? `(${hint})` : ''}`,
|
|
125
|
+
describeGet: (label, hint) => `查看${label}${hint ? `(${hint})` : ''}`,
|
|
126
|
+
configSubcommands: {
|
|
127
|
+
'/config set': '设置配置项',
|
|
128
|
+
'/config get': '查看配置项',
|
|
129
|
+
'/config list': '查看完整配置',
|
|
130
|
+
'/config reset': '重置为默认配置'
|
|
131
|
+
},
|
|
132
|
+
planSubcommands: {
|
|
133
|
+
'/plan <goal>': '创建一个人工审阅的实施计划',
|
|
134
|
+
'/plan auto <goal>': '自动生成计划并等待你确认执行',
|
|
135
|
+
'/plan auto run <goal>': '自动生成计划后立即继续执行',
|
|
136
|
+
'/plan approve': '批准当前待确认的计划并开始执行',
|
|
137
|
+
'/plan from-spec <spec-path?>': '从 spec 文件生成实施计划'
|
|
138
|
+
},
|
|
139
|
+
commands: {
|
|
140
|
+
help: '显示聊天帮助',
|
|
141
|
+
exit: '退出聊天',
|
|
142
|
+
commands: '列出 slash/自定义命令',
|
|
143
|
+
status: '查看运行状态(mode/model/session)',
|
|
144
|
+
mode: '设置执行模式:normal|auto|plan',
|
|
145
|
+
compact: '压缩消息上下文',
|
|
146
|
+
tasks: '任务面板管理',
|
|
147
|
+
checkpoint: '创建/查看/加载检查点',
|
|
148
|
+
spec: '在 .codemini/specs 中创建 spec',
|
|
149
|
+
plan: '在 .codemini/plans 中创建实施计划',
|
|
150
|
+
agents: '列出/运行子代理角色',
|
|
151
|
+
config: '设置/读取/列出/重置配置',
|
|
152
|
+
memory: '查看/搜索/删除持久记忆',
|
|
153
|
+
history: '查看/恢复会话',
|
|
154
|
+
debug: '运行时调试开关',
|
|
155
|
+
retry: '重试上一条用户请求'
|
|
156
|
+
},
|
|
157
|
+
generic: {
|
|
158
|
+
configCommand: '配置命令',
|
|
159
|
+
historyCommand: '历史会话命令',
|
|
160
|
+
modeCommand: '切换执行模式',
|
|
161
|
+
taskCommand: '任务面板命令',
|
|
162
|
+
checkpointCommand: '检查点命令',
|
|
163
|
+
specCommand: '创建 spec 文件',
|
|
164
|
+
planCommand: '规划命令',
|
|
165
|
+
agentCommand: '子代理命令',
|
|
166
|
+
memoryCommand: '记忆命令',
|
|
167
|
+
debugCommand: '调试命令',
|
|
168
|
+
keyboardDebugCommand: '键盘调试命令',
|
|
169
|
+
compactCommand: '上下文压缩命令',
|
|
170
|
+
retryCommand: '重试上一条用户请求',
|
|
171
|
+
statusCommand: '查看运行状态',
|
|
172
|
+
resumeSession: '恢复一个已保存的会话'
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
en: {
|
|
176
|
+
configLabels: {
|
|
177
|
+
'gateway.base_url': 'gateway base URL',
|
|
178
|
+
'gateway.api_key': 'gateway API key',
|
|
179
|
+
'sdk.provider': 'SDK provider',
|
|
180
|
+
'gateway.timeout_ms': 'gateway timeout in milliseconds',
|
|
181
|
+
'gateway.max_retries': 'gateway retry count',
|
|
182
|
+
'model.name': 'active model name',
|
|
183
|
+
'model.max_context_tokens': 'model context token limit',
|
|
184
|
+
'ui.language': 'UI language',
|
|
185
|
+
'ui.reply_language': 'reply language',
|
|
186
|
+
'execution.mode': 'execution mode',
|
|
187
|
+
'execution.always_allow_tools': 'always-allowed tools',
|
|
188
|
+
'execution.max_steps': 'maximum tool steps',
|
|
189
|
+
'context.preflight_trigger_pct': 'preflight compact threshold',
|
|
190
|
+
'context.hard_limit_pct': 'hard compact threshold',
|
|
191
|
+
'context.tool_result_max_chars': 'tool result character limit',
|
|
192
|
+
'context.read_file_default_lines': 'default read_file line window',
|
|
193
|
+
'context.read_file_max_chars': 'read_file character limit',
|
|
194
|
+
'sessions.max_sessions': 'stored session limit',
|
|
195
|
+
'sessions.retention_days': 'session retention days',
|
|
196
|
+
'shell.default': 'default shell',
|
|
197
|
+
'shell.timeout_ms': 'shell timeout in milliseconds',
|
|
198
|
+
'context.max_tokens': 'context token budget',
|
|
199
|
+
'soul.preset': 'soul preset',
|
|
200
|
+
'soul.custom_path': 'custom soul prompt path',
|
|
201
|
+
'policy.safe_mode': 'safe mode switch',
|
|
202
|
+
'policy.allow_dangerous_commands': 'dangerous command allowance'
|
|
203
|
+
},
|
|
204
|
+
optionHints: {
|
|
205
|
+
'sdk.provider': 'options: openai-compatible | anthropic',
|
|
206
|
+
'ui.language': 'options: zh | en',
|
|
207
|
+
'ui.reply_language': 'options: zh | en',
|
|
208
|
+
'execution.mode': 'options: auto | normal | plan',
|
|
209
|
+
'shell.default': 'common: bash | powershell',
|
|
210
|
+
'policy.safe_mode': 'options: true | false',
|
|
211
|
+
'policy.allow_dangerous_commands': 'options: true | false'
|
|
212
|
+
},
|
|
213
|
+
describeSet: (label, hint) => `set the ${label}${hint ? ` (${hint})` : ''}`,
|
|
214
|
+
describeGet: (label, hint) => `show the ${label}${hint ? ` (${hint})` : ''}`,
|
|
215
|
+
configSubcommands: {
|
|
216
|
+
'/config set': 'update a config value',
|
|
217
|
+
'/config get': 'show a config value',
|
|
218
|
+
'/config list': 'print the full config',
|
|
219
|
+
'/config reset': 'reset config to defaults'
|
|
220
|
+
},
|
|
221
|
+
planSubcommands: {
|
|
222
|
+
'/plan <goal>': 'create an implementation plan for manual review',
|
|
223
|
+
'/plan auto <goal>': 'generate a plan and wait for your approval',
|
|
224
|
+
'/plan auto run <goal>': 'generate a plan and continue execution immediately',
|
|
225
|
+
'/plan approve': 'approve the pending plan and start execution',
|
|
226
|
+
'/plan from-spec <spec-path?>': 'generate an implementation plan from a spec file'
|
|
227
|
+
},
|
|
228
|
+
commands: {
|
|
229
|
+
help: 'show chat help',
|
|
230
|
+
exit: 'exit chat',
|
|
231
|
+
commands: 'list slash/custom commands',
|
|
232
|
+
status: 'show runtime status (mode/model/session)',
|
|
233
|
+
mode: 'set execution mode: normal|auto|plan',
|
|
234
|
+
compact: 'compress message context',
|
|
235
|
+
tasks: 'task board management',
|
|
236
|
+
checkpoint: 'create/list/load conversation checkpoints',
|
|
237
|
+
spec: 'create a spec markdown file in .codemini/specs',
|
|
238
|
+
plan: 'create an implementation plan markdown file in .codemini/plans',
|
|
239
|
+
agents: 'run/list sub-agent roles',
|
|
240
|
+
config: 'set/get/list/reset config values',
|
|
241
|
+
memory: 'list/search/delete persistent memories',
|
|
242
|
+
history: 'list/resume sessions',
|
|
243
|
+
debug: 'runtime debug switches',
|
|
244
|
+
retry: 'retry the last user request'
|
|
245
|
+
},
|
|
246
|
+
generic: {
|
|
247
|
+
configCommand: 'config command',
|
|
248
|
+
historyCommand: 'history command',
|
|
249
|
+
modeCommand: 'switch execution mode',
|
|
250
|
+
taskCommand: 'task board command',
|
|
251
|
+
checkpointCommand: 'checkpoint command',
|
|
252
|
+
specCommand: 'create a spec file',
|
|
253
|
+
planCommand: 'planning command',
|
|
254
|
+
agentCommand: 'sub-agent command',
|
|
255
|
+
memoryCommand: 'memory command',
|
|
256
|
+
debugCommand: 'debug command',
|
|
257
|
+
keyboardDebugCommand: 'keyboard debug command',
|
|
258
|
+
compactCommand: 'context compaction command',
|
|
259
|
+
retryCommand: 'retry the last user request',
|
|
260
|
+
statusCommand: 'show runtime status',
|
|
261
|
+
resumeSession: 'resume a saved session'
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}[lang];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function describeConfigKey(key, mode = 'set', language = 'zh') {
|
|
268
|
+
const copy = getCompletionCopy(language);
|
|
269
|
+
const label = copy.configLabels[key] || key;
|
|
270
|
+
const hint = copy.optionHints[key] || '';
|
|
271
|
+
return mode === 'get' ? copy.describeGet(label, hint) : copy.describeSet(label, hint);
|
|
106
272
|
}
|
|
107
273
|
|
|
108
274
|
const SUB_AGENT_ROLES = ['planner', 'coder', 'reviewer', 'tester'];
|
|
@@ -407,6 +573,24 @@ function isLightweightAutoPlanGoal(goal, requirements = []) {
|
|
|
407
573
|
return /\b(add|update|fix|rename|trim|export|create|remove|change|implement)\b/i.test(text);
|
|
408
574
|
}
|
|
409
575
|
|
|
576
|
+
function classifyPlanTaskClass(goal = '') {
|
|
577
|
+
const text = String(goal || '').trim();
|
|
578
|
+
const lowerGoal = text.toLowerCase();
|
|
579
|
+
const advisory =
|
|
580
|
+
/\b(analyze|analysis|review|audit|inspect|assess|recommend|recommendation|optimization|optimize|improve|suggest|brainstorm|plan|feedback)\b/i.test(lowerGoal) ||
|
|
581
|
+
/(分析|审查|审计|检查|评估|建议|优化|优化点|优化建议|改进|改进点|规划|方案|看一下|看看|有哪些问题|有什么问题)/.test(text);
|
|
582
|
+
const implementation =
|
|
583
|
+
/\b(add|build|create|implement|support|introduce|refactor|rewrite|rework|migrate|change|update|fix)\b/i.test(lowerGoal) ||
|
|
584
|
+
/(新增|增加|实现|支持|重构|重写|改造|迁移|修改|更新|修复)/.test(text);
|
|
585
|
+
const verificationHeavy =
|
|
586
|
+
/\b(test|verify|validation|validate|prove|confirm|reproduce|check coverage)\b/i.test(lowerGoal) ||
|
|
587
|
+
/(测试|验证|校验|确认|复现|覆盖率)/.test(text);
|
|
588
|
+
|
|
589
|
+
if (verificationHeavy) return 'verification-heavy';
|
|
590
|
+
if (advisory && !implementation) return 'advisory';
|
|
591
|
+
return 'implementation';
|
|
592
|
+
}
|
|
593
|
+
|
|
410
594
|
function buildGoalRequirementPacket(goal, role) {
|
|
411
595
|
const rawGoal = trimInlineText(goal, 800);
|
|
412
596
|
if (!rawGoal) return '';
|
|
@@ -441,7 +625,9 @@ function buildAutoPlanPlannerGuidance() {
|
|
|
441
625
|
'- Prefer the smallest local approach that satisfies the goal.',
|
|
442
626
|
'- Do not output multiple alternative branches in the final plan.',
|
|
443
627
|
'- Do not assume implementation should begin before the plan is coherent.',
|
|
444
|
-
'-
|
|
628
|
+
'- Available sub-agent roles are planner, coder, reviewer, and tester. Use only the roles the task actually needs.',
|
|
629
|
+
'- For implementation-heavy or risky changes, prefer adding review and/or verification steps.',
|
|
630
|
+
'- For analysis, recommendation, or planning-only goals, you may omit reviewer/tester if they do not add value.',
|
|
445
631
|
'- Prefer 3-5 steps total unless the task is clearly larger.',
|
|
446
632
|
'- Keep the plan ordered, implementation-oriented, and easy for small sub-agents to follow.'
|
|
447
633
|
].join('\n');
|
|
@@ -598,49 +784,56 @@ function selectAutoSkillNames(text = '') {
|
|
|
598
784
|
return selected;
|
|
599
785
|
}
|
|
600
786
|
|
|
601
|
-
function
|
|
787
|
+
function classifyTaskComplexity(text = '') {
|
|
602
788
|
const input = String(text || '').trim();
|
|
603
|
-
if (!input) return
|
|
789
|
+
if (!input) return 'simple';
|
|
604
790
|
|
|
605
791
|
const lower = input.toLowerCase();
|
|
606
792
|
const explicitPlanning =
|
|
607
793
|
/(\/plan\b|plan first|make a plan|implementation plan|先做计划|先出方案|先规划|先计划)/i.test(lower);
|
|
608
|
-
if (explicitPlanning) return
|
|
794
|
+
if (explicitPlanning) return 'complex';
|
|
609
795
|
|
|
610
796
|
const simpleSkip =
|
|
611
797
|
/(typo|readme|console\.log|log this|rename\s+\w+|one line|small tweak|tiny fix|格式化|拼写|注释|文案|小改|微调)/i.test(
|
|
612
798
|
lower
|
|
613
799
|
);
|
|
614
|
-
if (simpleSkip) return
|
|
800
|
+
if (simpleSkip) return 'simple';
|
|
615
801
|
|
|
616
802
|
const discussionFirst =
|
|
617
803
|
/(brainstorm|头脑风暴|方案|思路|怎么做|如何做|which (?:approach|option|way)|best way|trade-?off|not sure|unsure|unclear|whether it should|要不要|不确定|先别写|先不要写|先讨论|先想一下)/i.test(
|
|
618
804
|
lower
|
|
619
805
|
);
|
|
620
|
-
if (discussionFirst) return
|
|
806
|
+
if (discussionFirst) return 'simple';
|
|
621
807
|
|
|
622
808
|
const implementationRequest =
|
|
623
809
|
/\b(add|build|create|implement|support|introduce|design|refactor|rework|migrate|change|update|rewrite|restructure)\b/i.test(
|
|
624
810
|
lower
|
|
625
811
|
) ||
|
|
626
812
|
/(新增|增加|实现|支持|设计|重构|改造|迁移|调整|重写|重做)/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);
|
|
813
|
+
if (!implementationRequest) return 'simple';
|
|
634
814
|
|
|
815
|
+
const broadSignalPattern =
|
|
816
|
+
/\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;
|
|
817
|
+
const broadSignals = lower.match(broadSignalPattern) || [];
|
|
635
818
|
const multipleActions = /\b(and|plus|also|while|along with)\b/i.test(lower) || /[,、;;].+/.test(input);
|
|
636
819
|
const singleFileScoped =
|
|
637
820
|
/\b(?:in|inside|within|only in)\s+[-_/.\w]+\.(?:[cm]?[jt]sx?|py|go|rb|java|rs|php|md)\b/i.test(lower) ||
|
|
638
821
|
/\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
|
-
|
|
822
|
+
const fileMentions = (lower.match(/[-_/.\w]+\.(?:[cm]?[jt]sx?|py|go|rb|java|rs|php|md)\b/g) || []).length;
|
|
823
|
+
const multiFileScope =
|
|
824
|
+
fileMentions >= 2 ||
|
|
825
|
+
/\b(across|multiple files?|cross-file|cross file)\b/i.test(lower) ||
|
|
826
|
+
/跨文件|多文件/.test(input);
|
|
827
|
+
const verificationHeavy = /\b(with tests?|and tests?|verify|validation|error handling|error recovery)\b/i.test(lower) || /测试|验证|校验|错误处理|错误恢复/.test(input);
|
|
828
|
+
const architectureHeavy =
|
|
829
|
+
broadSignals.length >= 3 ||
|
|
830
|
+
/\b(architecture|workflow|migration|state management|session state|integration)\b/i.test(lower) ||
|
|
831
|
+
/架构|流程|迁移|状态/.test(input);
|
|
832
|
+
|
|
833
|
+
if (singleFileScoped && !multipleActions && !verificationHeavy) return 'simple';
|
|
834
|
+
if (architectureHeavy && (multiFileScope || multipleActions || verificationHeavy)) return 'complex';
|
|
835
|
+
if (multiFileScope || verificationHeavy || multipleActions) return 'medium';
|
|
836
|
+
return 'simple';
|
|
644
837
|
}
|
|
645
838
|
|
|
646
839
|
function classifyAutoRoute(text = '') {
|
|
@@ -650,25 +843,42 @@ function classifyAutoRoute(text = '') {
|
|
|
650
843
|
return {
|
|
651
844
|
mode: 'brainstorm',
|
|
652
845
|
autoPlan: false,
|
|
653
|
-
selectedSkills
|
|
846
|
+
selectedSkills,
|
|
847
|
+
complexity: 'discussion'
|
|
654
848
|
};
|
|
655
849
|
}
|
|
656
850
|
|
|
657
|
-
|
|
851
|
+
const complexity = classifyTaskComplexity(text);
|
|
852
|
+
if (complexity === 'complex') {
|
|
658
853
|
return {
|
|
659
854
|
mode: 'auto_plan',
|
|
660
855
|
autoPlan: true,
|
|
661
|
-
selectedSkills: ['superpowers-lite']
|
|
856
|
+
selectedSkills: ['superpowers-lite'],
|
|
857
|
+
complexity
|
|
662
858
|
};
|
|
663
859
|
}
|
|
664
860
|
|
|
665
861
|
return {
|
|
666
|
-
mode: 'direct',
|
|
862
|
+
mode: complexity === 'medium' ? 'direct_medium' : 'direct',
|
|
667
863
|
autoPlan: false,
|
|
668
|
-
selectedSkills
|
|
864
|
+
selectedSkills,
|
|
865
|
+
complexity
|
|
669
866
|
};
|
|
670
867
|
}
|
|
671
868
|
|
|
869
|
+
function buildMediumTaskSystemPrompt(systemPrompt) {
|
|
870
|
+
const guidance = [
|
|
871
|
+
'Task Mode: medium',
|
|
872
|
+
'Execution guidance:',
|
|
873
|
+
'- Give a brief execution outline before coding.',
|
|
874
|
+
'- Keep the outline concise and focused on touched files/behaviors.',
|
|
875
|
+
'- Then implement directly instead of entering pending plan approval.',
|
|
876
|
+
'- Verify the changed behavior before finishing.',
|
|
877
|
+
'- If major ambiguity appears mid-task, say so clearly and ask for a plan instead of guessing.'
|
|
878
|
+
].join('\n');
|
|
879
|
+
return `${systemPrompt}\n\n${guidance}`;
|
|
880
|
+
}
|
|
881
|
+
|
|
672
882
|
function buildAutoSkillSystemPrompt(baseSystemPrompt, commands, config, text) {
|
|
673
883
|
const selected = classifyAutoRoute(text).selectedSkills.filter((name) => isSkillEnabled(config, name));
|
|
674
884
|
if (selected.length === 0) return baseSystemPrompt;
|
|
@@ -809,6 +1019,7 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
809
1019
|
const source = Array.isArray(plan?.steps) ? plan.steps : [];
|
|
810
1020
|
const requirements = deriveGoalRequirements(goal);
|
|
811
1021
|
const lightweightGoal = isLightweightAutoPlanGoal(goal, requirements);
|
|
1022
|
+
const taskClass = classifyPlanTaskClass(goal);
|
|
812
1023
|
const implementationSteps = source.filter((step) => step.role !== 'reviewer' && step.role !== 'tester');
|
|
813
1024
|
const primaryImplementationStep =
|
|
814
1025
|
implementationSteps.find((step) => step.role === 'coder') ||
|
|
@@ -827,17 +1038,31 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
827
1038
|
role: 'tester',
|
|
828
1039
|
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
1040
|
};
|
|
1041
|
+
const hasReviewer = source.some((step) => step.role === 'reviewer');
|
|
1042
|
+
const hasTester = source.some((step) => step.role === 'tester');
|
|
1043
|
+
|
|
1044
|
+
if (taskClass === 'advisory') {
|
|
1045
|
+
const advisorySteps = source.filter((step) => step.role === 'planner' || step.role === 'coder');
|
|
1046
|
+
return {
|
|
1047
|
+
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
1048
|
+
steps: advisorySteps.length > 0 ? advisorySteps.slice(0, 6) : [primaryImplementationStep]
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
830
1051
|
|
|
831
1052
|
if (lightweightGoal) {
|
|
832
1053
|
return {
|
|
833
1054
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
834
|
-
steps: [primaryImplementationStep, testerStep]
|
|
1055
|
+
steps: hasTester ? [primaryImplementationStep, testerStep] : [primaryImplementationStep]
|
|
835
1056
|
};
|
|
836
1057
|
}
|
|
837
1058
|
|
|
838
1059
|
return {
|
|
839
1060
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
840
|
-
steps: [
|
|
1061
|
+
steps: [
|
|
1062
|
+
...implementationSteps.slice(0, 6),
|
|
1063
|
+
...(hasReviewer ? [reviewerStep] : []),
|
|
1064
|
+
...(testerStep ? [testerStep] : [])
|
|
1065
|
+
]
|
|
841
1066
|
};
|
|
842
1067
|
}
|
|
843
1068
|
|
|
@@ -906,19 +1131,23 @@ function extractAcceptanceStatusItems(text = '') {
|
|
|
906
1131
|
}
|
|
907
1132
|
|
|
908
1133
|
function buildAutoPlanSystemSummary(auto) {
|
|
909
|
-
const
|
|
1134
|
+
const baseStatusTitle =
|
|
910
1135
|
auto.failedCount > 0 ? 'Auto plan finished with failures' : auto.warningCount > 0 ? 'Auto plan finished with warnings' : 'Auto plan finished';
|
|
1136
|
+
const statusTitle =
|
|
1137
|
+
auto.approvalStatus === 'pending' ? `${baseStatusTitle} (waiting for /plan approve)` : baseStatusTitle;
|
|
911
1138
|
const lines = [
|
|
912
1139
|
statusTitle,
|
|
913
|
-
`File: ${auto.filePath}`,
|
|
1140
|
+
`Plan File: ${auto.filePath}`,
|
|
914
1141
|
`Plan Summary: ${auto.summary || '-'}`,
|
|
915
1142
|
`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}`
|
|
1143
|
+
`Approval: ${auto.approvalStatus || 'not_required'}`
|
|
921
1144
|
];
|
|
1145
|
+
if (auto.approvalStatus !== 'pending') {
|
|
1146
|
+
lines.push(`Steps: ${auto.steps.length} total`);
|
|
1147
|
+
lines.push(`Completed: ${auto.completedCount}`);
|
|
1148
|
+
lines.push(`Warnings: ${auto.warningCount}`);
|
|
1149
|
+
lines.push(`Failed: ${auto.failedCount}`);
|
|
1150
|
+
}
|
|
922
1151
|
if (auto.warningTitles?.length) {
|
|
923
1152
|
lines.push(`Warning steps: ${auto.warningTitles.slice(0, 5).join(', ')}`);
|
|
924
1153
|
}
|
|
@@ -926,7 +1155,7 @@ function buildAutoPlanSystemSummary(auto) {
|
|
|
926
1155
|
lines.push(`Failed steps: ${auto.failedTitles.slice(0, 5).join(', ')}`);
|
|
927
1156
|
}
|
|
928
1157
|
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.');
|
|
1158
|
+
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
1159
|
}
|
|
931
1160
|
return lines.join('\n');
|
|
932
1161
|
}
|
|
@@ -1002,6 +1231,7 @@ async function buildAutoPlanFinalSummary({
|
|
|
1002
1231
|
|
|
1003
1232
|
try {
|
|
1004
1233
|
const result = await createChatCompletion({
|
|
1234
|
+
sdkProvider: config.sdk?.provider,
|
|
1005
1235
|
baseUrl: config.gateway.base_url,
|
|
1006
1236
|
apiKey: config.gateway.api_key,
|
|
1007
1237
|
model: model || config.model.name,
|
|
@@ -1102,6 +1332,7 @@ async function buildSpecWithModel({
|
|
|
1102
1332
|
].join('\n');
|
|
1103
1333
|
|
|
1104
1334
|
const result = await createChatCompletion({
|
|
1335
|
+
sdkProvider: config.sdk?.provider,
|
|
1105
1336
|
baseUrl: config.gateway.base_url,
|
|
1106
1337
|
apiKey: config.gateway.api_key,
|
|
1107
1338
|
model: model || config.model.name,
|
|
@@ -1161,6 +1392,7 @@ async function buildPlanFromSpecWithModel({
|
|
|
1161
1392
|
].join('\n');
|
|
1162
1393
|
|
|
1163
1394
|
const result = await createChatCompletion({
|
|
1395
|
+
sdkProvider: config.sdk?.provider,
|
|
1164
1396
|
baseUrl: config.gateway.base_url,
|
|
1165
1397
|
apiKey: config.gateway.api_key,
|
|
1166
1398
|
model: model || config.model.name,
|
|
@@ -1276,6 +1508,7 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
1276
1508
|
const snapshot = {
|
|
1277
1509
|
sessionId: currentSession?.id || '',
|
|
1278
1510
|
mode: executionMode || config.execution?.mode || 'auto',
|
|
1511
|
+
sdkProvider: config.sdk?.provider || 'openai-compatible',
|
|
1279
1512
|
model: model || config.model?.name || '',
|
|
1280
1513
|
maxContextTokens
|
|
1281
1514
|
};
|
|
@@ -1336,7 +1569,7 @@ function buildPendingPlanApprovalMessage(planState) {
|
|
|
1336
1569
|
const lines = [
|
|
1337
1570
|
'Plan approval is still pending.',
|
|
1338
1571
|
`Goal: ${planState?.goal || '-'}`,
|
|
1339
|
-
`Plan
|
|
1572
|
+
`Plan File: ${planState?.filePath || '-'}`,
|
|
1340
1573
|
`Summary: ${planState?.finalSummary || planState?.summary || '-'}`,
|
|
1341
1574
|
'Use /plan approve to start implementation, or /plan stay to keep refining the plan first.'
|
|
1342
1575
|
];
|
|
@@ -1344,6 +1577,7 @@ function buildPendingPlanApprovalMessage(planState) {
|
|
|
1344
1577
|
}
|
|
1345
1578
|
|
|
1346
1579
|
function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
|
|
1580
|
+
const requirementPacket = buildGoalRequirementPacket(planState?.goal || '', 'coder');
|
|
1347
1581
|
const lines = [
|
|
1348
1582
|
'Approved implementation plan:',
|
|
1349
1583
|
`Original goal: ${planState?.goal || '-'}`,
|
|
@@ -1351,6 +1585,7 @@ function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
|
|
|
1351
1585
|
`Plan summary: ${planState?.summary || '-'}`,
|
|
1352
1586
|
`Final planning summary: ${planState?.finalSummary || planState?.summary || '-'}`,
|
|
1353
1587
|
`User approval: ${String(approvalText || '').trim() || 'approved'}`,
|
|
1588
|
+
requirementPacket,
|
|
1354
1589
|
Array.isArray(planState?.steps) && planState.steps.length > 0 ? 'Planned steps:' : '',
|
|
1355
1590
|
...(Array.isArray(planState?.steps)
|
|
1356
1591
|
? planState.steps.slice(0, 8).map((step, index) => `${index + 1}. [${step.role}] ${step.title} :: ${step.task}`)
|
|
@@ -1595,6 +1830,7 @@ async function askModel({
|
|
|
1595
1830
|
};
|
|
1596
1831
|
|
|
1597
1832
|
const result = await createChatCompletionStream({
|
|
1833
|
+
sdkProvider: config.sdk?.provider,
|
|
1598
1834
|
baseUrl: config.gateway.base_url,
|
|
1599
1835
|
apiKey: config.gateway.api_key,
|
|
1600
1836
|
model: selectedModel,
|
|
@@ -1719,12 +1955,26 @@ async function buildAutoPlanAndRun({
|
|
|
1719
1955
|
model,
|
|
1720
1956
|
systemPrompt,
|
|
1721
1957
|
onAgentEvent,
|
|
1722
|
-
sessionId
|
|
1958
|
+
sessionId,
|
|
1959
|
+
taskClass
|
|
1723
1960
|
}) {
|
|
1961
|
+
const normalizedTaskClass = taskClass || classifyPlanTaskClass(goal);
|
|
1724
1962
|
const requirementPacket = buildGoalRequirementPacket(goal, 'planner');
|
|
1725
1963
|
const plannerPrompt = [
|
|
1726
1964
|
buildAutoPlanPlannerGuidance(),
|
|
1727
|
-
'
|
|
1965
|
+
'Planning policy:',
|
|
1966
|
+
'- First classify the user goal as one of: advisory, implementation, or verification-heavy.',
|
|
1967
|
+
'- advisory = analysis, review, audit, optimization suggestions, architecture feedback, brainstorming, planning, or recommendation requests.',
|
|
1968
|
+
'- implementation = add/build/create/implement/refactor/fix/update/change behavior in code or files.',
|
|
1969
|
+
'- verification-heavy = the user explicitly asks to run tests, verify findings, reproduce a bug, prove a claim, or validate a result.',
|
|
1970
|
+
'- 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.',
|
|
1971
|
+
'- For advisory goals, do not emit generic filler steps such as "Test and verify", "Review recommendations", or other template-only steps.',
|
|
1972
|
+
'- For implementation goals, reviewer and tester are optional support roles, not defaults. Only include them when they clearly add value.',
|
|
1973
|
+
'- 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.',
|
|
1974
|
+
'- If the task is purely to inspect the current project and suggest improvements, a lean 2-step or 3-step plan is preferred.',
|
|
1975
|
+
'- Example advisory roles: planner -> inspect project shape, coder -> synthesize findings and prioritized recommendations.',
|
|
1976
|
+
'- Example implementation roles: planner -> inspect target area, coder -> implement change, tester -> verify changed behavior.',
|
|
1977
|
+
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester","task":"..."}]}. No markdown.'
|
|
1728
1978
|
].join('\n');
|
|
1729
1979
|
let autoPlan = {
|
|
1730
1980
|
summary: `Auto plan for: ${goal}`,
|
|
@@ -1739,6 +1989,7 @@ async function buildAutoPlanAndRun({
|
|
|
1739
1989
|
let planningError = '';
|
|
1740
1990
|
try {
|
|
1741
1991
|
const planning = await createChatCompletion({
|
|
1992
|
+
sdkProvider: config.sdk?.provider,
|
|
1742
1993
|
baseUrl: config.gateway.base_url,
|
|
1743
1994
|
apiKey: config.gateway.api_key,
|
|
1744
1995
|
model: model || config.model.name,
|
|
@@ -1749,10 +2000,15 @@ async function buildAutoPlanAndRun({
|
|
|
1749
2000
|
content: [
|
|
1750
2001
|
'Create an execution plan and assign best sub-agent role for each step.',
|
|
1751
2002
|
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester","task":"..."}]}. No markdown.',
|
|
1752
|
-
'
|
|
2003
|
+
'The available roles are planner, coder, reviewer, and tester. Use only the roles the task actually needs.',
|
|
2004
|
+
`Task class: ${normalizedTaskClass}`,
|
|
2005
|
+
'Before choosing roles, decide whether the request is advisory, implementation, or verification-heavy.',
|
|
1753
2006
|
requirementPacket,
|
|
1754
2007
|
'The first step should usually inspect or clarify the target area before implementation.',
|
|
1755
|
-
'
|
|
2008
|
+
'For analysis, recommendation, optimization, audit, or project-review goals, keep the plan lean and usually limit it to planner/coder.',
|
|
2009
|
+
'Do not include reviewer/tester for advisory goals unless the user explicitly asks to validate, verify, or independently review the findings.',
|
|
2010
|
+
'Avoid template-only titles like "Initial analysis", "Review recommendations", or "Test and verify" for advisory goals.',
|
|
2011
|
+
'For implementation-heavy changes, prefer review and/or testing steps near the end only when they materially improve confidence.',
|
|
1756
2012
|
'Prefer 3-5 steps total.'
|
|
1757
2013
|
]
|
|
1758
2014
|
.filter(Boolean)
|
|
@@ -1891,14 +2147,43 @@ export async function createChatRuntime({
|
|
|
1891
2147
|
messageCount: Array.isArray(currentSession.messages) ? currentSession.messages.length : 0
|
|
1892
2148
|
}
|
|
1893
2149
|
];
|
|
2150
|
+
try {
|
|
2151
|
+
const initialSessions = await listSessions(100);
|
|
2152
|
+
if (initialSessions.length > 0) {
|
|
2153
|
+
const merged = [
|
|
2154
|
+
{
|
|
2155
|
+
id: currentSession.id,
|
|
2156
|
+
messageCount: Array.isArray(currentSession.messages) ? currentSession.messages.length : 0
|
|
2157
|
+
},
|
|
2158
|
+
...initialSessions.map((session) => ({
|
|
2159
|
+
id: session.id,
|
|
2160
|
+
messageCount: Number(session.messageCount || 0)
|
|
2161
|
+
}))
|
|
2162
|
+
];
|
|
2163
|
+
const deduped = [];
|
|
2164
|
+
const seen = new Set();
|
|
2165
|
+
for (const session of merged) {
|
|
2166
|
+
const id = String(session.id || '').trim();
|
|
2167
|
+
if (!id || seen.has(id)) continue;
|
|
2168
|
+
seen.add(id);
|
|
2169
|
+
deduped.push(session);
|
|
2170
|
+
}
|
|
2171
|
+
historySessionCache = deduped;
|
|
2172
|
+
historyIdCache = deduped.map((session) => session.id);
|
|
2173
|
+
}
|
|
2174
|
+
} catch {
|
|
2175
|
+
// keep startup resilient even if historical sessions cannot be listed
|
|
2176
|
+
}
|
|
1894
2177
|
|
|
1895
2178
|
const configKeyHints = [
|
|
1896
2179
|
'gateway.base_url',
|
|
1897
2180
|
'gateway.api_key',
|
|
1898
2181
|
'model.name',
|
|
2182
|
+
'ui.language',
|
|
1899
2183
|
'ui.reply_language',
|
|
1900
2184
|
'execution.mode',
|
|
1901
2185
|
'shell.default',
|
|
2186
|
+
'sdk.provider',
|
|
1902
2187
|
'gateway.timeout_ms',
|
|
1903
2188
|
'gateway.max_retries',
|
|
1904
2189
|
'model.max_context_tokens',
|
|
@@ -1923,6 +2208,7 @@ export async function createChatRuntime({
|
|
|
1923
2208
|
'/help',
|
|
1924
2209
|
'/status',
|
|
1925
2210
|
'/config',
|
|
2211
|
+
'/memory',
|
|
1926
2212
|
'/mode',
|
|
1927
2213
|
'/plan',
|
|
1928
2214
|
'/tasks',
|
|
@@ -1934,30 +2220,26 @@ export async function createChatRuntime({
|
|
|
1934
2220
|
'/retry'
|
|
1935
2221
|
];
|
|
1936
2222
|
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
2223
|
|
|
1944
2224
|
const listCommandNames = () => {
|
|
2225
|
+
const completionCopy = getCompletionCopy(config.ui?.language);
|
|
1945
2226
|
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: 'tasks', description:
|
|
1953
|
-
{ name: 'checkpoint', description:
|
|
1954
|
-
{ name: 'spec', description:
|
|
1955
|
-
{ name: 'plan', description:
|
|
1956
|
-
{ name: 'agents', description:
|
|
1957
|
-
{ name: 'config', description:
|
|
1958
|
-
{ name: '
|
|
1959
|
-
{ name: '
|
|
1960
|
-
{ name: '
|
|
2227
|
+
{ name: 'help', description: completionCopy.commands.help },
|
|
2228
|
+
{ name: 'exit', description: completionCopy.commands.exit },
|
|
2229
|
+
{ name: 'commands', description: completionCopy.commands.commands },
|
|
2230
|
+
{ name: 'status', description: completionCopy.commands.status },
|
|
2231
|
+
{ name: 'mode', description: completionCopy.commands.mode },
|
|
2232
|
+
{ name: 'compact', description: completionCopy.commands.compact },
|
|
2233
|
+
{ name: 'tasks', description: completionCopy.commands.tasks },
|
|
2234
|
+
{ name: 'checkpoint', description: completionCopy.commands.checkpoint },
|
|
2235
|
+
{ name: 'spec', description: completionCopy.commands.spec },
|
|
2236
|
+
{ name: 'plan', description: completionCopy.commands.plan },
|
|
2237
|
+
{ name: 'agents', description: completionCopy.commands.agents },
|
|
2238
|
+
{ name: 'config', description: completionCopy.commands.config },
|
|
2239
|
+
{ name: 'memory', description: completionCopy.commands.memory },
|
|
2240
|
+
{ name: 'history', description: completionCopy.commands.history },
|
|
2241
|
+
{ name: 'debug', description: completionCopy.commands.debug },
|
|
2242
|
+
{ name: 'retry', description: completionCopy.commands.retry }
|
|
1961
2243
|
];
|
|
1962
2244
|
const out = [];
|
|
1963
2245
|
for (const cmd of commands.values()) {
|
|
@@ -1991,6 +2273,7 @@ export async function createChatRuntime({
|
|
|
1991
2273
|
];
|
|
1992
2274
|
|
|
1993
2275
|
const historyTemplates = ['/history list', '/history current', '/history resume <session_id>'];
|
|
2276
|
+
const memoryTemplates = ['/memory list <scope>', '/memory search <scope> <query>', '/memory forget <scope> <id>'];
|
|
1994
2277
|
const modeTemplates = ['/mode normal', '/mode auto', '/mode plan'];
|
|
1995
2278
|
const taskTemplates = ['/tasks', '/tasks add <title>', '/tasks start <id>', '/tasks done <id>', '/tasks remove <id>', '/tasks clear'];
|
|
1996
2279
|
const checkpointTemplates = [
|
|
@@ -2000,12 +2283,13 @@ export async function createChatRuntime({
|
|
|
2000
2283
|
'/checkpoint load <id>'
|
|
2001
2284
|
];
|
|
2002
2285
|
const specTemplates = ['/spec <topic>'];
|
|
2003
|
-
const planTemplates = ['/plan <goal>', '/plan auto <goal>', '/plan from-spec <spec-path?>'];
|
|
2286
|
+
const planTemplates = ['/plan <goal>', '/plan auto <goal>', '/plan auto run <goal>', '/plan approve', '/plan from-spec <spec-path?>'];
|
|
2004
2287
|
const agentTemplates = ['/agents list', '/agents run planner <task>', '/agents run coder <task>', '/agents run reviewer <task>', '/agents run tester <task>'];
|
|
2005
2288
|
const debugTemplates = ['/debug keys on', '/debug keys off', '/debug keys status'];
|
|
2006
2289
|
const compactTemplates = compactOptions.map((opt) => `/compact ${opt}`);
|
|
2007
2290
|
const slashTemplates = [
|
|
2008
2291
|
...configTemplates,
|
|
2292
|
+
...memoryTemplates,
|
|
2009
2293
|
...historyTemplates,
|
|
2010
2294
|
...modeTemplates,
|
|
2011
2295
|
...taskTemplates,
|
|
@@ -2041,6 +2325,9 @@ export async function createChatRuntime({
|
|
|
2041
2325
|
const getCompletionOptions = (rawInput) => {
|
|
2042
2326
|
const input = String(rawInput || '');
|
|
2043
2327
|
if (!input.startsWith('/')) return [];
|
|
2328
|
+
const completionCopy = getCompletionCopy(config.ui?.language);
|
|
2329
|
+
const configSubcommandDescriptions = completionCopy.configSubcommands;
|
|
2330
|
+
const planSubcommandDescriptions = completionCopy.planSubcommands || {};
|
|
2044
2331
|
|
|
2045
2332
|
const hasTrailingSpace = /\s$/.test(input);
|
|
2046
2333
|
const body = input.slice(1);
|
|
@@ -2048,6 +2335,7 @@ export async function createChatRuntime({
|
|
|
2048
2335
|
const commandPart = tokens[0] || '';
|
|
2049
2336
|
const commandHasSubcommands = new Set([
|
|
2050
2337
|
'config',
|
|
2338
|
+
'memory',
|
|
2051
2339
|
'compact',
|
|
2052
2340
|
'mode',
|
|
2053
2341
|
'tasks',
|
|
@@ -2065,19 +2353,22 @@ export async function createChatRuntime({
|
|
|
2065
2353
|
registerSuggestion(`/${entry.name}`, entry.description || '');
|
|
2066
2354
|
}
|
|
2067
2355
|
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
|
|
2074
|
-
for (const template of
|
|
2075
|
-
for (const template of
|
|
2076
|
-
for (const template of
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2356
|
+
registerSuggestion(template, configSubcommandDescriptions[template] || completionCopy.generic.configCommand);
|
|
2357
|
+
}
|
|
2358
|
+
for (const template of memoryTemplates) registerSuggestion(template, completionCopy.generic.memoryCommand);
|
|
2359
|
+
for (const template of historyTemplates) registerSuggestion(template, completionCopy.generic.historyCommand);
|
|
2360
|
+
for (const template of modeTemplates) registerSuggestion(template, completionCopy.generic.modeCommand);
|
|
2361
|
+
for (const template of taskTemplates) registerSuggestion(template, completionCopy.generic.taskCommand);
|
|
2362
|
+
for (const template of checkpointTemplates) registerSuggestion(template, completionCopy.generic.checkpointCommand);
|
|
2363
|
+
for (const template of specTemplates) registerSuggestion(template, completionCopy.generic.specCommand);
|
|
2364
|
+
for (const template of planTemplates) {
|
|
2365
|
+
registerSuggestion(template, planSubcommandDescriptions[template] || completionCopy.generic.planCommand);
|
|
2366
|
+
}
|
|
2367
|
+
for (const template of agentTemplates) registerSuggestion(template, completionCopy.generic.agentCommand);
|
|
2368
|
+
for (const template of debugTemplates) registerSuggestion(template, completionCopy.generic.debugCommand);
|
|
2369
|
+
for (const template of compactTemplates) registerSuggestion(template, completionCopy.generic.compactCommand);
|
|
2370
|
+
registerSuggestion('/retry', completionCopy.generic.retryCommand);
|
|
2371
|
+
registerSuggestion('/status', completionCopy.generic.statusCommand);
|
|
2081
2372
|
|
|
2082
2373
|
if (!commandPart) {
|
|
2083
2374
|
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
@@ -2105,7 +2396,7 @@ export async function createChatRuntime({
|
|
|
2105
2396
|
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
2106
2397
|
['set', 'get', 'list', 'reset']
|
|
2107
2398
|
.filter((s) => s.startsWith(subcommand))
|
|
2108
|
-
.map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] ||
|
|
2399
|
+
.map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] || completionCopy.generic.configCommand).value),
|
|
2109
2400
|
configSubcommandPriority
|
|
2110
2401
|
));
|
|
2111
2402
|
}
|
|
@@ -2114,42 +2405,58 @@ export async function createChatRuntime({
|
|
|
2114
2405
|
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
2115
2406
|
return configKeyHints
|
|
2116
2407
|
.filter((k) => k.startsWith(keyPrefix))
|
|
2117
|
-
.map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get')));
|
|
2408
|
+
.map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get', config.ui?.language)));
|
|
2118
2409
|
}
|
|
2119
2410
|
if (subcommand === 'set') {
|
|
2120
2411
|
const keyPrefix = tokens.length >= 3 ? tokens[2] || '' : '';
|
|
2121
2412
|
return configKeyHints
|
|
2122
2413
|
.filter((k) => k.startsWith(keyPrefix))
|
|
2123
|
-
.map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set')));
|
|
2414
|
+
.map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set', config.ui?.language)));
|
|
2124
2415
|
}
|
|
2125
2416
|
|
|
2126
2417
|
return materializeSuggestions(configTemplates);
|
|
2127
2418
|
}
|
|
2128
2419
|
|
|
2420
|
+
if (commandPart === 'memory') {
|
|
2421
|
+
const sub = tokens[1] || '';
|
|
2422
|
+
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2423
|
+
return ['list', 'search', 'forget']
|
|
2424
|
+
.filter((item) => item.startsWith(sub))
|
|
2425
|
+
.map((item) => registerSuggestion(`/memory ${item}`, completionCopy.generic.memoryCommand));
|
|
2426
|
+
}
|
|
2427
|
+
const scope = tokens[2] || '';
|
|
2428
|
+
if (['list', 'search', 'forget'].includes(sub) && (tokens.length === 2 || (tokens.length === 3 && !hasTrailingSpace))) {
|
|
2429
|
+
return ['user', 'global', 'project']
|
|
2430
|
+
.filter((item) => item.startsWith(scope))
|
|
2431
|
+
.map((item) => registerSuggestion(`/memory ${sub} ${item}${sub === 'list' ? '' : ' '}`, completionCopy.generic.memoryCommand));
|
|
2432
|
+
}
|
|
2433
|
+
return materializeSuggestions(memoryTemplates);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2129
2436
|
if (commandPart === 'compact') {
|
|
2130
2437
|
const joined = tokens.slice(1).join(' ');
|
|
2131
2438
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2132
2439
|
return compactOptions
|
|
2133
2440
|
.filter((opt) => opt.startsWith(joined) || joined === '')
|
|
2134
|
-
.map((opt) => registerSuggestion(`/compact ${opt}`,
|
|
2441
|
+
.map((opt) => registerSuggestion(`/compact ${opt}`, completionCopy.generic.compactCommand));
|
|
2135
2442
|
}
|
|
2136
2443
|
return compactOptions
|
|
2137
2444
|
.filter((opt) => opt.includes(joined) || joined === '')
|
|
2138
|
-
.map((opt) => registerSuggestion(`/compact ${opt}`,
|
|
2445
|
+
.map((opt) => registerSuggestion(`/compact ${opt}`, completionCopy.generic.compactCommand));
|
|
2139
2446
|
}
|
|
2140
2447
|
|
|
2141
2448
|
if (commandPart === 'retry') {
|
|
2142
|
-
return [registerSuggestion('/retry',
|
|
2449
|
+
return [registerSuggestion('/retry', completionCopy.generic.retryCommand)];
|
|
2143
2450
|
}
|
|
2144
2451
|
if (commandPart === 'status') {
|
|
2145
|
-
return [registerSuggestion('/status',
|
|
2452
|
+
return [registerSuggestion('/status', completionCopy.generic.statusCommand)];
|
|
2146
2453
|
}
|
|
2147
2454
|
if (commandPart === 'mode') {
|
|
2148
2455
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2149
2456
|
const sub = tokens[1] || '';
|
|
2150
2457
|
return ['normal', 'auto', 'plan']
|
|
2151
2458
|
.filter((m) => m.startsWith(sub))
|
|
2152
|
-
.map((m) => registerSuggestion(`/mode ${m}`,
|
|
2459
|
+
.map((m) => registerSuggestion(`/mode ${m}`, completionCopy.generic.modeCommand));
|
|
2153
2460
|
}
|
|
2154
2461
|
return materializeSuggestions(modeTemplates);
|
|
2155
2462
|
}
|
|
@@ -2158,7 +2465,7 @@ export async function createChatRuntime({
|
|
|
2158
2465
|
const sub = tokens[1] || '';
|
|
2159
2466
|
return ['add', 'start', 'done', 'remove', 'rm', 'clear']
|
|
2160
2467
|
.filter((s) => s.startsWith(sub))
|
|
2161
|
-
.map((s) => registerSuggestion(`/tasks ${s}`,
|
|
2468
|
+
.map((s) => registerSuggestion(`/tasks ${s}`, completionCopy.generic.taskCommand));
|
|
2162
2469
|
}
|
|
2163
2470
|
return materializeSuggestions(taskTemplates);
|
|
2164
2471
|
}
|
|
@@ -2167,24 +2474,24 @@ export async function createChatRuntime({
|
|
|
2167
2474
|
const sub = tokens[1] || '';
|
|
2168
2475
|
if (sub === 'list') {
|
|
2169
2476
|
return ['--all']
|
|
2170
|
-
.map((v) => registerSuggestion(`/checkpoint list ${v}`,
|
|
2477
|
+
.map((v) => registerSuggestion(`/checkpoint list ${v}`, completionCopy.generic.checkpointCommand));
|
|
2171
2478
|
}
|
|
2172
2479
|
return ['create', 'list', 'load']
|
|
2173
2480
|
.filter((s) => s.startsWith(sub))
|
|
2174
|
-
.map((s) => registerSuggestion(`/checkpoint ${s}`,
|
|
2481
|
+
.map((s) => registerSuggestion(`/checkpoint ${s}`, completionCopy.generic.checkpointCommand));
|
|
2175
2482
|
}
|
|
2176
2483
|
if (tokens[1] === 'list') {
|
|
2177
2484
|
const hint = tokens[2] || '';
|
|
2178
2485
|
return ['--all']
|
|
2179
2486
|
.filter((v) => v.startsWith(hint))
|
|
2180
|
-
.map((v) => registerSuggestion(`/checkpoint list ${v}`,
|
|
2487
|
+
.map((v) => registerSuggestion(`/checkpoint list ${v}`, completionCopy.generic.checkpointCommand));
|
|
2181
2488
|
}
|
|
2182
2489
|
if (tokens[1] === 'load') {
|
|
2183
2490
|
if (tokens.length >= 3) {
|
|
2184
2491
|
const hint = tokens[3] || '';
|
|
2185
2492
|
return ['--all']
|
|
2186
2493
|
.filter((v) => v.startsWith(hint))
|
|
2187
|
-
.map((v) => registerSuggestion(`/checkpoint load ${tokens[2]} ${v}`,
|
|
2494
|
+
.map((v) => registerSuggestion(`/checkpoint load ${tokens[2]} ${v}`, completionCopy.generic.checkpointCommand));
|
|
2188
2495
|
}
|
|
2189
2496
|
}
|
|
2190
2497
|
return materializeSuggestions(checkpointTemplates);
|
|
@@ -2193,11 +2500,25 @@ export async function createChatRuntime({
|
|
|
2193
2500
|
return materializeSuggestions(specTemplates);
|
|
2194
2501
|
}
|
|
2195
2502
|
if (commandPart === 'plan') {
|
|
2503
|
+
if (tokens[1] === 'auto' && (tokens.length === 2 || (tokens.length === 3 && !hasTrailingSpace))) {
|
|
2504
|
+
const sub = tokens[2] || '';
|
|
2505
|
+
return ['run']
|
|
2506
|
+
.filter((s) => s.startsWith(sub))
|
|
2507
|
+
.map((s) => registerSuggestion(`/plan auto ${s} `, planSubcommandDescriptions[`/plan auto ${s} <goal>`] || completionCopy.generic.planCommand));
|
|
2508
|
+
}
|
|
2196
2509
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2197
2510
|
const sub = tokens[1] || '';
|
|
2198
|
-
return ['auto', 'from-spec']
|
|
2511
|
+
return ['auto', 'approve', 'from-spec']
|
|
2199
2512
|
.filter((s) => s.startsWith(sub))
|
|
2200
|
-
.map((s) =>
|
|
2513
|
+
.map((s) =>
|
|
2514
|
+
registerSuggestion(
|
|
2515
|
+
`/plan ${s}`,
|
|
2516
|
+
planSubcommandDescriptions[`/plan ${s}`] ||
|
|
2517
|
+
planSubcommandDescriptions[`/plan ${s} <goal>`] ||
|
|
2518
|
+
planSubcommandDescriptions[`/plan ${s} <spec-path?>`] ||
|
|
2519
|
+
completionCopy.generic.planCommand
|
|
2520
|
+
)
|
|
2521
|
+
);
|
|
2201
2522
|
}
|
|
2202
2523
|
return materializeSuggestions(planTemplates);
|
|
2203
2524
|
}
|
|
@@ -2206,17 +2527,17 @@ export async function createChatRuntime({
|
|
|
2206
2527
|
const sub = tokens[1] || '';
|
|
2207
2528
|
if (sub === 'run') {
|
|
2208
2529
|
return ['planner', 'coder', 'reviewer', 'tester']
|
|
2209
|
-
.map((r) => registerSuggestion(`/agents run ${r} `,
|
|
2530
|
+
.map((r) => registerSuggestion(`/agents run ${r} `, completionCopy.generic.agentCommand));
|
|
2210
2531
|
}
|
|
2211
2532
|
return ['list', 'run']
|
|
2212
2533
|
.filter((s) => s.startsWith(sub))
|
|
2213
|
-
.map((s) => registerSuggestion(`/agents ${s}`,
|
|
2534
|
+
.map((s) => registerSuggestion(`/agents ${s}`, completionCopy.generic.agentCommand));
|
|
2214
2535
|
}
|
|
2215
2536
|
if (tokens[1] === 'run') {
|
|
2216
2537
|
const rolePrefix = tokens[2] || '';
|
|
2217
2538
|
return ['planner', 'coder', 'reviewer', 'tester']
|
|
2218
2539
|
.filter((r) => r.startsWith(rolePrefix))
|
|
2219
|
-
.map((r) => registerSuggestion(`/agents run ${r} `,
|
|
2540
|
+
.map((r) => registerSuggestion(`/agents run ${r} `, completionCopy.generic.agentCommand));
|
|
2220
2541
|
}
|
|
2221
2542
|
return materializeSuggestions(agentTemplates);
|
|
2222
2543
|
}
|
|
@@ -2230,13 +2551,13 @@ export async function createChatRuntime({
|
|
|
2230
2551
|
.map((session) => ({
|
|
2231
2552
|
value: `/history resume ${session.id}`,
|
|
2232
2553
|
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
|
|
2233
|
-
description:
|
|
2554
|
+
description: completionCopy.generic.resumeSession
|
|
2234
2555
|
}));
|
|
2235
2556
|
if (dynamic.length > 0) return dynamic;
|
|
2236
2557
|
}
|
|
2237
2558
|
return ['list', 'current', 'resume']
|
|
2238
2559
|
.filter((s) => s.startsWith(sub))
|
|
2239
|
-
.map((s) => registerSuggestion(`/history ${s}`,
|
|
2560
|
+
.map((s) => registerSuggestion(`/history ${s}`, completionCopy.generic.historyCommand));
|
|
2240
2561
|
}
|
|
2241
2562
|
if (sub === 'resume') {
|
|
2242
2563
|
const idPrefix = tokens[2] || '';
|
|
@@ -2245,7 +2566,7 @@ export async function createChatRuntime({
|
|
|
2245
2566
|
.map((session) => ({
|
|
2246
2567
|
value: `/history resume ${session.id}`,
|
|
2247
2568
|
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
|
|
2248
|
-
description:
|
|
2569
|
+
description: completionCopy.generic.resumeSession
|
|
2249
2570
|
}));
|
|
2250
2571
|
if (dynamic.length > 0) return dynamic;
|
|
2251
2572
|
return materializeSuggestions(historyTemplates);
|
|
@@ -2258,17 +2579,17 @@ export async function createChatRuntime({
|
|
|
2258
2579
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
2259
2580
|
if (sub === 'keys') {
|
|
2260
2581
|
return ['on', 'off', 'status']
|
|
2261
|
-
.map((v) => registerSuggestion(`/debug keys ${v}`,
|
|
2582
|
+
.map((v) => registerSuggestion(`/debug keys ${v}`, completionCopy.generic.keyboardDebugCommand));
|
|
2262
2583
|
}
|
|
2263
2584
|
return ['keys']
|
|
2264
2585
|
.filter((s) => s.startsWith(sub))
|
|
2265
|
-
.map((s) => registerSuggestion(`/debug ${s}`,
|
|
2586
|
+
.map((s) => registerSuggestion(`/debug ${s}`, completionCopy.generic.debugCommand));
|
|
2266
2587
|
}
|
|
2267
2588
|
if (sub === 'keys') {
|
|
2268
2589
|
const action = tokens[2] || '';
|
|
2269
2590
|
return ['on', 'off', 'status']
|
|
2270
2591
|
.filter((v) => v.startsWith(action))
|
|
2271
|
-
.map((v) => registerSuggestion(`/debug keys ${v}`,
|
|
2592
|
+
.map((v) => registerSuggestion(`/debug keys ${v}`, completionCopy.generic.keyboardDebugCommand));
|
|
2272
2593
|
}
|
|
2273
2594
|
return materializeSuggestions(debugTemplates);
|
|
2274
2595
|
}
|
|
@@ -2286,6 +2607,17 @@ export async function createChatRuntime({
|
|
|
2286
2607
|
await saveSession(currentSession);
|
|
2287
2608
|
};
|
|
2288
2609
|
|
|
2610
|
+
const buildActiveSystemPrompt = async () => {
|
|
2611
|
+
const soulPrompt = await buildSystemPromptWithSoul(baseSystemPrompt, config);
|
|
2612
|
+
const memorySnapshot = await buildMemorySnapshot({
|
|
2613
|
+
config,
|
|
2614
|
+
workspaceRoot: process.cwd()
|
|
2615
|
+
}).catch(() => '');
|
|
2616
|
+
const memoryGuide =
|
|
2617
|
+
'Persistent memory is for durable preferences, project conventions, and stable workflow knowledge. Use fresh file reads when the code can verify details. Only write memory when the fact is likely to matter in future sessions, is stable over time, and is not sensitive. Do not store temporary task details, speculative guesses, or secrets. Choose remember_user for user preferences, communication habits, and long-term constraints. Choose remember_global for reusable workflow knowledge that helps across many projects. Choose remember_project for repository-specific conventions, architecture notes, important modules, and local workflow expectations. When memory includes command names, file paths, identifiers, or exact wording, preserve those tokens exactly instead of paraphrasing them.';
|
|
2618
|
+
return [soulPrompt, memorySnapshot, memoryGuide].filter(Boolean).join('\n\n');
|
|
2619
|
+
};
|
|
2620
|
+
|
|
2289
2621
|
const isImmediateLocalInput = (line) => {
|
|
2290
2622
|
const parsedInput = parseInput(line);
|
|
2291
2623
|
if (parsedInput.type !== 'slash') return false;
|
|
@@ -2304,6 +2636,7 @@ export async function createChatRuntime({
|
|
|
2304
2636
|
'tasks',
|
|
2305
2637
|
'checkpoint',
|
|
2306
2638
|
'history',
|
|
2639
|
+
'memory',
|
|
2307
2640
|
'config',
|
|
2308
2641
|
'compact',
|
|
2309
2642
|
'debug'
|
|
@@ -2312,8 +2645,7 @@ export async function createChatRuntime({
|
|
|
2312
2645
|
};
|
|
2313
2646
|
|
|
2314
2647
|
const submit = async (line, onAgentEvent) => {
|
|
2315
|
-
const
|
|
2316
|
-
const activeReplySystemPrompt = await buildSystemPromptWithSoul(baseSystemPrompt, config);
|
|
2648
|
+
const activeReplySystemPrompt = await buildActiveSystemPrompt();
|
|
2317
2649
|
try {
|
|
2318
2650
|
await appendInputHistory(line);
|
|
2319
2651
|
} catch {
|
|
@@ -2332,7 +2664,7 @@ export async function createChatRuntime({
|
|
|
2332
2664
|
if (parsedInput.command === 'help') {
|
|
2333
2665
|
return {
|
|
2334
2666
|
type: 'system',
|
|
2335
|
-
text: 'Commands: /help /exit /commands /status /mode /compact /tasks /checkpoint /spec /plan /agents /config /history /debug /retry /<custom> !<shell>'
|
|
2667
|
+
text: 'Commands: /help /exit /commands /status /mode /compact /tasks /checkpoint /spec /plan /agents /config /memory /history /debug /retry /<custom> !<shell>'
|
|
2336
2668
|
};
|
|
2337
2669
|
}
|
|
2338
2670
|
if (parsedInput.command === 'status') {
|
|
@@ -2486,7 +2818,7 @@ export async function createChatRuntime({
|
|
|
2486
2818
|
topic,
|
|
2487
2819
|
config,
|
|
2488
2820
|
model,
|
|
2489
|
-
systemPrompt:
|
|
2821
|
+
systemPrompt: activeReplySystemPrompt
|
|
2490
2822
|
});
|
|
2491
2823
|
} catch (err) {
|
|
2492
2824
|
content = buildSpecTemplate(topic);
|
|
@@ -2506,17 +2838,45 @@ export async function createChatRuntime({
|
|
|
2506
2838
|
if (parsedInput.command === 'plan') {
|
|
2507
2839
|
const sub = (parsedInput.args[0] || '').trim().toLowerCase();
|
|
2508
2840
|
if (sub === 'auto') {
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2841
|
+
const runImmediately = (parsedInput.args[1] || '').trim().toLowerCase() === 'run';
|
|
2842
|
+
const goal = parsedInput.args.slice(runImmediately ? 2 : 1).join(' ').trim();
|
|
2843
|
+
if (!goal) return { type: 'system', text: 'Usage: /plan auto <goal> | /plan auto run <goal>' };
|
|
2511
2844
|
const auto = await buildAutoPlanAndRun({
|
|
2512
2845
|
goal,
|
|
2513
2846
|
session: currentSession,
|
|
2514
2847
|
config,
|
|
2515
2848
|
model,
|
|
2516
|
-
systemPrompt:
|
|
2849
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2517
2850
|
onAgentEvent,
|
|
2518
|
-
sessionId: currentSession.id
|
|
2851
|
+
sessionId: currentSession.id,
|
|
2852
|
+
taskClass: classifyPlanTaskClass(goal)
|
|
2519
2853
|
});
|
|
2854
|
+
if (runImmediately) {
|
|
2855
|
+
const result = await askModel({
|
|
2856
|
+
text: buildApprovedPlanExecutionPrompt(
|
|
2857
|
+
{
|
|
2858
|
+
status: 'approved',
|
|
2859
|
+
source: 'auto',
|
|
2860
|
+
goal,
|
|
2861
|
+
filePath: auto.filePath,
|
|
2862
|
+
summary: auto.summary || '',
|
|
2863
|
+
finalSummary: auto.finalSummary || auto.summary || '',
|
|
2864
|
+
steps: Array.isArray(auto.steps) ? auto.steps : []
|
|
2865
|
+
},
|
|
2866
|
+
'/plan auto run'
|
|
2867
|
+
),
|
|
2868
|
+
session: currentSession,
|
|
2869
|
+
config,
|
|
2870
|
+
model,
|
|
2871
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2872
|
+
onAgentEvent,
|
|
2873
|
+
executionMode: 'auto'
|
|
2874
|
+
});
|
|
2875
|
+
currentSession.planState = null;
|
|
2876
|
+
executionMode = 'auto';
|
|
2877
|
+
await saveSession(currentSession);
|
|
2878
|
+
return { type: 'assistant', text: result.text };
|
|
2879
|
+
}
|
|
2520
2880
|
currentSession.planState = {
|
|
2521
2881
|
status: 'pending_approval',
|
|
2522
2882
|
source: 'auto',
|
|
@@ -2577,7 +2937,7 @@ export async function createChatRuntime({
|
|
|
2577
2937
|
specPath,
|
|
2578
2938
|
config,
|
|
2579
2939
|
model,
|
|
2580
|
-
systemPrompt:
|
|
2940
|
+
systemPrompt: activeReplySystemPrompt
|
|
2581
2941
|
});
|
|
2582
2942
|
} catch (err) {
|
|
2583
2943
|
planContent = buildPlanTemplate(specTitle);
|
|
@@ -2596,7 +2956,7 @@ export async function createChatRuntime({
|
|
|
2596
2956
|
}
|
|
2597
2957
|
|
|
2598
2958
|
const goal = parsedInput.args.join(' ').trim();
|
|
2599
|
-
if (!goal) return { type: 'system', text: 'Usage: /plan <goal> | /plan auto <goal> | /plan from-spec <spec-path?>' };
|
|
2959
|
+
if (!goal) return { type: 'system', text: 'Usage: /plan <goal> | /plan auto <goal> | /plan auto run <goal> | /plan from-spec <spec-path?>' };
|
|
2600
2960
|
const content = buildPlanTemplate(goal);
|
|
2601
2961
|
const filePath = await writeMarkdownInProjectDir(
|
|
2602
2962
|
'plans',
|
|
@@ -2630,7 +2990,7 @@ export async function createChatRuntime({
|
|
|
2630
2990
|
parentSession: currentSession,
|
|
2631
2991
|
config,
|
|
2632
2992
|
model,
|
|
2633
|
-
systemPrompt:
|
|
2993
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2634
2994
|
onAgentEvent
|
|
2635
2995
|
});
|
|
2636
2996
|
const text = `[sub-agent:${role}]\n${output.text || output}`;
|
|
@@ -2691,11 +3051,55 @@ export async function createChatRuntime({
|
|
|
2691
3051
|
];
|
|
2692
3052
|
return {
|
|
2693
3053
|
type: 'system',
|
|
2694
|
-
text: `Switched to session: ${targetId} (${loaded.messages.length} messages)
|
|
3054
|
+
text: `Switched to session: ${targetId} (${loaded.messages.length} messages)`,
|
|
3055
|
+
restoredMessages: structuredClone(loaded.messages || [])
|
|
2695
3056
|
};
|
|
2696
3057
|
}
|
|
2697
3058
|
return { type: 'system', text: `Unknown /history subcommand: ${sub}` };
|
|
2698
3059
|
}
|
|
3060
|
+
if (parsedInput.command === 'memory') {
|
|
3061
|
+
const sub = String(parsedInput.args[0] || '').trim().toLowerCase();
|
|
3062
|
+
if (!sub) {
|
|
3063
|
+
return { type: 'system', text: 'Usage: /memory list <user|global|project> | /memory search <scope> <query> | /memory forget <scope> <id>' };
|
|
3064
|
+
}
|
|
3065
|
+
if (sub === 'list') {
|
|
3066
|
+
const scope = String(parsedInput.args[1] || '').trim().toLowerCase();
|
|
3067
|
+
if (!['user', 'global', 'project'].includes(scope)) {
|
|
3068
|
+
return { type: 'system', text: 'Usage: /memory list <user|global|project>' };
|
|
3069
|
+
}
|
|
3070
|
+
const items = await listMemories({ scope, workspaceRoot: process.cwd() });
|
|
3071
|
+
if (items.length === 0) return { type: 'system', text: `No ${scope} memories found.` };
|
|
3072
|
+
return {
|
|
3073
|
+
type: 'system',
|
|
3074
|
+
text: items.map((item) => `${item.id} | ${item.kind} | ${item.content}`).join('\n')
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
if (sub === 'search') {
|
|
3078
|
+
const scope = String(parsedInput.args[1] || '').trim().toLowerCase();
|
|
3079
|
+
const query = parsedInput.args.slice(2).join(' ').trim();
|
|
3080
|
+
if (!['user', 'global', 'project'].includes(scope) || !query) {
|
|
3081
|
+
return { type: 'system', text: 'Usage: /memory search <user|global|project> <query>' };
|
|
3082
|
+
}
|
|
3083
|
+
const items = await searchMemories({ scope, query, workspaceRoot: process.cwd() });
|
|
3084
|
+
if (items.length === 0) return { type: 'system', text: `No ${scope} memories matched: ${query}` };
|
|
3085
|
+
return {
|
|
3086
|
+
type: 'system',
|
|
3087
|
+
text: items.map((item) => `${item.id} | ${item.kind} | ${item.content}`).join('\n')
|
|
3088
|
+
};
|
|
3089
|
+
}
|
|
3090
|
+
if (sub === 'forget') {
|
|
3091
|
+
const scope = String(parsedInput.args[1] || '').trim().toLowerCase();
|
|
3092
|
+
const id = String(parsedInput.args[2] || '').trim();
|
|
3093
|
+
if (!['user', 'global', 'project'].includes(scope) || !id) {
|
|
3094
|
+
return { type: 'system', text: 'Usage: /memory forget <user|global|project> <id>' };
|
|
3095
|
+
}
|
|
3096
|
+
const result = await forgetMemory({ scope, id, workspaceRoot: process.cwd() });
|
|
3097
|
+
const text = `Removed ${Number(result.removed || 0)} ${scope} memory item(s)`;
|
|
3098
|
+
await persistLocalExchange(line, text, { includeUser: false });
|
|
3099
|
+
return { type: 'system', text };
|
|
3100
|
+
}
|
|
3101
|
+
return { type: 'system', text: `Unknown /memory subcommand: ${sub}` };
|
|
3102
|
+
}
|
|
2699
3103
|
if (parsedInput.command === 'retry') {
|
|
2700
3104
|
const lastUser = [...currentSession.messages].reverse().find((m) => m.role === 'user');
|
|
2701
3105
|
if (!lastUser?.content) {
|
|
@@ -2842,7 +3246,7 @@ export async function createChatRuntime({
|
|
|
2842
3246
|
session: currentSession,
|
|
2843
3247
|
config,
|
|
2844
3248
|
model,
|
|
2845
|
-
systemPrompt:
|
|
3249
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2846
3250
|
onAgentEvent,
|
|
2847
3251
|
executionMode
|
|
2848
3252
|
});
|
|
@@ -2924,9 +3328,10 @@ export async function createChatRuntime({
|
|
|
2924
3328
|
session: currentSession,
|
|
2925
3329
|
config,
|
|
2926
3330
|
model,
|
|
2927
|
-
systemPrompt:
|
|
3331
|
+
systemPrompt: activeReplySystemPrompt,
|
|
2928
3332
|
onAgentEvent,
|
|
2929
|
-
sessionId: currentSession.id
|
|
3333
|
+
sessionId: currentSession.id,
|
|
3334
|
+
taskClass: classifyPlanTaskClass(expandedText)
|
|
2930
3335
|
});
|
|
2931
3336
|
currentSession.planState = {
|
|
2932
3337
|
status: 'pending_approval',
|
|
@@ -2950,7 +3355,11 @@ export async function createChatRuntime({
|
|
|
2950
3355
|
names: selectedAutoSkills
|
|
2951
3356
|
});
|
|
2952
3357
|
}
|
|
2953
|
-
const
|
|
3358
|
+
const skillPrompt = buildAutoSkillSystemPrompt(activeReplySystemPrompt, commands, config, expandedText);
|
|
3359
|
+
const routedSystemPrompt =
|
|
3360
|
+
autoRoute.mode === 'direct_medium'
|
|
3361
|
+
? buildMediumTaskSystemPrompt(skillPrompt)
|
|
3362
|
+
: skillPrompt;
|
|
2954
3363
|
const result = await askModel({
|
|
2955
3364
|
text: expandedText,
|
|
2956
3365
|
session: currentSession,
|