codemini-cli 0.4.1 → 0.4.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 -2
- package/README.md +87 -7
- package/deployment.md +14 -7
- package/package.json +1 -3
- package/skills/grill-me/SKILL.md +30 -0
- package/skills/project-requirements/SKILL.md +245 -0
- package/skills/superpowers-lite/SKILL.md +5 -1
- package/src/cli.js +1 -1
- package/src/commands/run.js +5 -4
- package/src/commands/skill.js +145 -53
- package/src/core/agent-loop.js +9 -214
- package/src/core/chat-runtime.js +520 -78
- package/src/core/command-loader.js +12 -5
- package/src/core/config-store.js +6 -3
- package/src/core/context-compact.js +2 -1
- package/src/core/dream-audit.js +12 -0
- package/src/core/dream-consolidate.js +131 -59
- package/src/core/dream-evaluator.js +86 -0
- package/src/core/fff-adapter.js +1 -1
- package/src/core/memory-store.js +145 -10
- package/src/core/provider/anthropic.js +2 -2
- package/src/core/provider/openai-compatible.js +2 -2
- package/src/core/reflect-skill.js +178 -0
- package/src/core/shell.js +1 -1
- package/src/core/tool-result-store.js +206 -0
- package/src/core/tools.js +242 -69
- package/src/tui/chat-app.js +298 -48
- package/src/tui/tool-activity/presenters/system.js +6 -0
- package/src/core/provider/anthropic.sdk-backup.js +0 -439
- package/src/core/provider/openai-compatible.sdk-backup.js +0 -412
package/src/core/chat-runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseInput } from './input-parser.js';
|
|
2
2
|
import { loadCommandsAndSkills, renderCommandPrompt } from './command-loader.js';
|
|
3
|
-
import { runAgentLoop
|
|
3
|
+
import { runAgentLoop } from './agent-loop.js';
|
|
4
|
+
import { setResultDir, clearResultStore } from './tool-result-store.js';
|
|
4
5
|
import { trimInline, normalizePath } from './string-utils.js';
|
|
5
6
|
import fs from 'node:fs/promises';
|
|
6
7
|
import path from 'node:path';
|
|
@@ -29,6 +30,12 @@ import { forgetMemory, listMemories, rememberMemory, searchMemories, captureToIn
|
|
|
29
30
|
import { runDreamConsolidation } from './dream-consolidate.js';
|
|
30
31
|
import { normalizePlanState } from './plan-state.js';
|
|
31
32
|
import { countActiveTodos, normalizeTodos } from './todo-state.js';
|
|
33
|
+
import {
|
|
34
|
+
attachReflectTargets,
|
|
35
|
+
buildReflectSkillDraft,
|
|
36
|
+
parseReflectScope,
|
|
37
|
+
writeReflectSkillDraft
|
|
38
|
+
} from './reflect-skill.js';
|
|
32
39
|
|
|
33
40
|
const STREAM_SAVE_DEBOUNCE_MS = 120;
|
|
34
41
|
|
|
@@ -103,6 +110,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
103
110
|
'context.tool_result_max_chars': '工具结果字符上限',
|
|
104
111
|
'context.read_file_default_lines': 'read_file 默认行数窗口',
|
|
105
112
|
'context.read_file_max_chars': 'read_file 字符上限',
|
|
113
|
+
'context.prompt_budget_audit': 'Prompt 预算审计开关',
|
|
106
114
|
'sessions.max_sessions': '会话保留上限',
|
|
107
115
|
'sessions.retention_days': '会话保留天数',
|
|
108
116
|
'shell.default': '默认 shell',
|
|
@@ -120,7 +128,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
120
128
|
'execution.mode': '可选:auto | normal | plan',
|
|
121
129
|
'shell.default': '常用:bash | powershell',
|
|
122
130
|
'policy.safe_mode': '可选:true | false',
|
|
123
|
-
'policy.allow_dangerous_commands': '可选:true | false'
|
|
131
|
+
'policy.allow_dangerous_commands': '可选:true | false',
|
|
132
|
+
'context.prompt_budget_audit': '可选:true | false'
|
|
124
133
|
},
|
|
125
134
|
describeSet: (label, hint) => `设置${label}${hint ? `(${hint})` : ''}`,
|
|
126
135
|
describeGet: (label, hint) => `查看${label}${hint ? `(${hint})` : ''}`,
|
|
@@ -153,12 +162,14 @@ function getCompletionCopy(language = 'zh') {
|
|
|
153
162
|
config: '设置/读取/列出/重置配置',
|
|
154
163
|
memory: '查看/搜索/删除持久记忆',
|
|
155
164
|
dream: '整理记忆收件箱(dream consolidation)',
|
|
165
|
+
reflect: '复盘成功链路并生成可审阅 skill 草稿',
|
|
156
166
|
history: '查看/恢复会话',
|
|
157
167
|
debug: '运行时调试开关',
|
|
158
168
|
retry: '重试上一条用户请求',
|
|
159
169
|
stop: '中止当前回答',
|
|
160
170
|
new: '开始新会话',
|
|
161
171
|
yes: '确认当前待审批计划并开始执行',
|
|
172
|
+
no: '放弃当前待审批事项',
|
|
162
173
|
edit: '修改当前待审批计划',
|
|
163
174
|
reject: '拒绝当前待审批计划'
|
|
164
175
|
},
|
|
@@ -172,6 +183,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
172
183
|
agentCommand: '子代理命令',
|
|
173
184
|
memoryCommand: '记忆命令',
|
|
174
185
|
dreamCommand: '记忆整理命令',
|
|
186
|
+
reflectCommand: '复盘生成 skill 草稿',
|
|
175
187
|
debugCommand: '调试命令',
|
|
176
188
|
keyboardDebugCommand: '键盘调试命令',
|
|
177
189
|
compactCommand: '上下文压缩命令',
|
|
@@ -200,6 +212,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
200
212
|
'context.tool_result_max_chars': 'tool result character limit',
|
|
201
213
|
'context.read_file_default_lines': 'default read_file line window',
|
|
202
214
|
'context.read_file_max_chars': 'read_file character limit',
|
|
215
|
+
'context.prompt_budget_audit': 'prompt budget audit switch',
|
|
203
216
|
'sessions.max_sessions': 'stored session limit',
|
|
204
217
|
'sessions.retention_days': 'session retention days',
|
|
205
218
|
'shell.default': 'default shell',
|
|
@@ -217,7 +230,8 @@ function getCompletionCopy(language = 'zh') {
|
|
|
217
230
|
'execution.mode': 'options: auto | normal | plan',
|
|
218
231
|
'shell.default': 'common: bash | powershell',
|
|
219
232
|
'policy.safe_mode': 'options: true | false',
|
|
220
|
-
'policy.allow_dangerous_commands': 'options: true | false'
|
|
233
|
+
'policy.allow_dangerous_commands': 'options: true | false',
|
|
234
|
+
'context.prompt_budget_audit': 'options: true | false'
|
|
221
235
|
},
|
|
222
236
|
describeSet: (label, hint) => `set the ${label}${hint ? ` (${hint})` : ''}`,
|
|
223
237
|
describeGet: (label, hint) => `show the ${label}${hint ? ` (${hint})` : ''}`,
|
|
@@ -250,12 +264,14 @@ function getCompletionCopy(language = 'zh') {
|
|
|
250
264
|
config: 'set/get/list/reset config values',
|
|
251
265
|
memory: 'list/search/delete persistent memories',
|
|
252
266
|
dream: 'consolidate memory inbox (dream)',
|
|
267
|
+
reflect: 'reflect on a successful workflow and draft a reusable skill',
|
|
253
268
|
history: 'list/resume sessions',
|
|
254
269
|
debug: 'runtime debug switches',
|
|
255
270
|
retry: 'retry the last user request',
|
|
256
271
|
stop: 'stop the current response',
|
|
257
272
|
new: 'start a new session',
|
|
258
273
|
yes: 'approve the pending plan and start execution',
|
|
274
|
+
no: 'discard the pending item',
|
|
259
275
|
edit: 'revise the pending plan',
|
|
260
276
|
reject: 'reject the pending plan'
|
|
261
277
|
},
|
|
@@ -269,6 +285,7 @@ function getCompletionCopy(language = 'zh') {
|
|
|
269
285
|
agentCommand: 'sub-agent command',
|
|
270
286
|
memoryCommand: 'memory command',
|
|
271
287
|
dreamCommand: 'dream consolidation command',
|
|
288
|
+
reflectCommand: 'reflect skill draft command',
|
|
272
289
|
debugCommand: 'debug command',
|
|
273
290
|
keyboardDebugCommand: 'keyboard debug command',
|
|
274
291
|
compactCommand: 'context compaction command',
|
|
@@ -288,9 +305,10 @@ function describeConfigKey(key, mode = 'set', language = 'zh') {
|
|
|
288
305
|
return mode === 'get' ? copy.describeGet(label, hint) : copy.describeSet(label, hint);
|
|
289
306
|
}
|
|
290
307
|
|
|
291
|
-
const SUB_AGENT_ROLES = ['planner', 'coder', 'reviewer', 'tester', 'summarizer'];
|
|
308
|
+
const SUB_AGENT_ROLES = ['planner', 'advisor', 'coder', 'reviewer', 'tester', 'summarizer'];
|
|
292
309
|
export const ROLE_TOOL_POLICY = {
|
|
293
310
|
planner: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'glob', 'ast_query', 'read_ast_node', 'web_fetch', 'web_search', 'read_plan', 'update_plan'],
|
|
311
|
+
advisor: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'read_plan'],
|
|
294
312
|
coder: ['read', 'grep', 'list', 'edit', 'write', 'delete', 'run', 'ast_query', 'read_ast_node', 'glob', 'tool_search', 'web_fetch', 'web_search', 'update_todos', 'read_plan', 'update_plan'],
|
|
295
313
|
reviewer: ['read', 'grep', 'list', 'glob', 'tool_search', 'ast_query', 'read_ast_node', 'read_plan'],
|
|
296
314
|
tester: ['read', 'grep', 'list', 'run', 'glob', 'tool_search', 'read_plan'],
|
|
@@ -338,6 +356,25 @@ export function getSubAgentRolePrompt(role) {
|
|
|
338
356
|
'Do not add a closing summary or "Next Action" — the pipeline handles what comes next.'
|
|
339
357
|
].join('\n');
|
|
340
358
|
}
|
|
359
|
+
if (role === 'advisor') {
|
|
360
|
+
return [
|
|
361
|
+
'You are the advisor in a multi-step agent pipeline.',
|
|
362
|
+
'Your job: analyze the handed-off context and produce recommendations, tradeoffs, and evidence.',
|
|
363
|
+
'Do not edit files, write code, delete files, or run commands.',
|
|
364
|
+
'Output format — keep it short and direct:',
|
|
365
|
+
'Findings:',
|
|
366
|
+
'- <important observation, constraint, or "none">',
|
|
367
|
+
'Recommendations:',
|
|
368
|
+
'- <prioritized recommendation or "none">',
|
|
369
|
+
'Tradeoffs:',
|
|
370
|
+
'- <important tradeoff or "none">',
|
|
371
|
+
'Evidence:',
|
|
372
|
+
'- <files, docs, or observations checked>',
|
|
373
|
+
'Open Questions:',
|
|
374
|
+
'- <blocking uncertainty or "none">',
|
|
375
|
+
'Do not summarize your own work or add closing remarks — just deliver the structured advisory handoff and stop.'
|
|
376
|
+
].join('\n');
|
|
377
|
+
}
|
|
341
378
|
if (role === 'tester') {
|
|
342
379
|
return [
|
|
343
380
|
'You are the tester in a multi-step agent pipeline.',
|
|
@@ -413,7 +450,10 @@ function buildPipelineStepGuidance({ role, stepIndex, totalSteps, isFirst, isLas
|
|
|
413
450
|
lines.push('- If you discover something new, record it under the requested headings instead of burying it in prose.');
|
|
414
451
|
lines.push('- Continue the established direction unless you have concrete contradictory evidence.');
|
|
415
452
|
lines.push('- Output only what the next step needs to know. Skip obvious observations.');
|
|
416
|
-
if (
|
|
453
|
+
if (role !== 'summarizer') {
|
|
454
|
+
lines.push('- Do not produce a final overall summary; the final summarizer step owns synthesis.');
|
|
455
|
+
}
|
|
456
|
+
if (isLast && role === 'summarizer') {
|
|
417
457
|
lines.push('- Since you are the final step, give a concise overall verdict the user can act on.');
|
|
418
458
|
}
|
|
419
459
|
return lines.join('\n');
|
|
@@ -704,6 +744,9 @@ function buildGoalRequirementPacket(goal, role) {
|
|
|
704
744
|
lines.push('Verify the implementation against the original goal, not just syntax or smoke checks.');
|
|
705
745
|
lines.push('Check each acceptance item explicitly before calling the work verified.');
|
|
706
746
|
lines.push('If any requested behavior is unverified or contradicted by evidence, list it under Not Verified or Failures.');
|
|
747
|
+
} else if (role === 'advisor') {
|
|
748
|
+
lines.push('Advise against the acceptance checklist and original goal, not generic best practices.');
|
|
749
|
+
lines.push('Prioritize concrete recommendations, evidence, and tradeoffs. Do not implement changes.');
|
|
707
750
|
} else if (role === 'coder') {
|
|
708
751
|
lines.push('Implement against the acceptance checklist, not only the broad wording of the goal.');
|
|
709
752
|
}
|
|
@@ -719,10 +762,11 @@ function buildAutoPlanPlannerGuidance() {
|
|
|
719
762
|
'- Prefer the smallest local approach that satisfies the goal.',
|
|
720
763
|
'- Do not output multiple alternative branches in the final plan.',
|
|
721
764
|
'- Do not assume implementation should begin before the plan is coherent.',
|
|
722
|
-
'- Available sub-agent roles are planner, coder, reviewer, tester, and summarizer. Use only the roles the task actually needs.',
|
|
723
|
-
'- The summarizer
|
|
765
|
+
'- Available sub-agent roles are planner, advisor, coder, reviewer, tester, and summarizer. Use only the non-summary roles the task actually needs.',
|
|
766
|
+
'- Always include a summarizer as the final step. The summarizer reads accumulated step results from the plan file and synthesizes the final summary. It does NOT re-analyze the codebase.',
|
|
767
|
+
'- Do not ask planner, advisor, coder, reviewer, or tester steps to produce the final summary. They should write detailed step results for the summarizer.',
|
|
724
768
|
'- For implementation-heavy or risky changes, prefer adding review and/or verification steps.',
|
|
725
|
-
'- For analysis, recommendation, or planning-only goals,
|
|
769
|
+
'- For analysis, recommendation, optimization, architecture feedback, or planning-only goals, prefer advisor over coder and omit reviewer/tester if they do not add value.',
|
|
726
770
|
'- Prefer 3-5 steps total unless the task is clearly larger.',
|
|
727
771
|
'- Keep the plan ordered, implementation-oriented, and easy for small sub-agents to follow.'
|
|
728
772
|
].join('\n');
|
|
@@ -740,6 +784,9 @@ function buildAutoPlanExecutionGuidance(role) {
|
|
|
740
784
|
if (role === 'coder') {
|
|
741
785
|
common.push('- Keep edits tightly scoped to the chosen plan direction.');
|
|
742
786
|
common.push('- Avoid speculative cleanup or unrelated improvements.');
|
|
787
|
+
} else if (role === 'advisor') {
|
|
788
|
+
common.push('- Produce advisory findings and recommendations only; do not modify files or run commands.');
|
|
789
|
+
common.push('- Ground every recommendation in inspected evidence or mark it as an assumption.');
|
|
743
790
|
} else if (role === 'reviewer') {
|
|
744
791
|
common.push('- Review against the chosen plan direction and the acceptance checklist.');
|
|
745
792
|
common.push('- Call out missing requested behavior, regression risk, and unverified claims.');
|
|
@@ -1040,7 +1087,12 @@ async function buildTesterVerificationPacket(focusPaths = []) {
|
|
|
1040
1087
|
return lines.join('\n');
|
|
1041
1088
|
}
|
|
1042
1089
|
|
|
1043
|
-
function
|
|
1090
|
+
function isBundledSkillCommand(command) {
|
|
1091
|
+
return command?.metadata?.type === 'skill' && command?.source === 'bundled-skill';
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function isSkillEnabled(config, name, command = null) {
|
|
1095
|
+
if (isBundledSkillCommand(command)) return true;
|
|
1044
1096
|
return config.skills?.enabled?.[name] !== false;
|
|
1045
1097
|
}
|
|
1046
1098
|
|
|
@@ -1048,6 +1100,10 @@ function selectAutoSkillNames(text = '') {
|
|
|
1048
1100
|
const input = String(text || '').toLowerCase();
|
|
1049
1101
|
const selected = ['superpowers-lite'];
|
|
1050
1102
|
|
|
1103
|
+
const explicitGrillMe =
|
|
1104
|
+
/\bgr+ill\s+me\b|\bpressure[- ]?test\b|\bstress[- ]?test\b|\bchallenge\s+(?:this|my|me)\b|\btear\s+(?:this|my)\s+.*apart\b/i.test(
|
|
1105
|
+
input
|
|
1106
|
+
) || /(拷问|质询|压力测试|挑战一下|挑刺|狠狠审|喷一下|怼一下)/i.test(input);
|
|
1051
1107
|
const explicitBrainstorm =
|
|
1052
1108
|
/(brainstorm|头脑风暴|方案|思路|设计一下|设计方案|怎么做|如何做|approach|options?)/i.test(input);
|
|
1053
1109
|
const ambiguitySignals =
|
|
@@ -1066,6 +1122,9 @@ function selectAutoSkillNames(text = '') {
|
|
|
1066
1122
|
if (explicitBrainstorm || (ambiguitySignals && featureRequest) || greenfieldBuildRequest) {
|
|
1067
1123
|
selected.push('brainstorm');
|
|
1068
1124
|
}
|
|
1125
|
+
if (explicitGrillMe) {
|
|
1126
|
+
selected.push('grill-me');
|
|
1127
|
+
}
|
|
1069
1128
|
return selected;
|
|
1070
1129
|
}
|
|
1071
1130
|
|
|
@@ -1165,7 +1224,7 @@ function buildMediumTaskSystemPrompt(systemPrompt) {
|
|
|
1165
1224
|
}
|
|
1166
1225
|
|
|
1167
1226
|
function buildAutoSkillSystemPrompt(baseSystemPrompt, commands, config, text) {
|
|
1168
|
-
const selected = classifyAutoRoute(text).selectedSkills.filter((name) => isSkillEnabled(config, name));
|
|
1227
|
+
const selected = classifyAutoRoute(text).selectedSkills.filter((name) => isSkillEnabled(config, name, commands.get(name)));
|
|
1169
1228
|
if (selected.length === 0) return baseSystemPrompt;
|
|
1170
1229
|
|
|
1171
1230
|
const blocks = [];
|
|
@@ -1244,13 +1303,34 @@ function summarizeGoalForStepTitle(goal, fallback = 'requested change') {
|
|
|
1244
1303
|
function buildFallbackAutoPlan(goal) {
|
|
1245
1304
|
const requirements = deriveGoalRequirements(goal);
|
|
1246
1305
|
const lightweightGoal = isLightweightAutoPlanGoal(goal, requirements);
|
|
1306
|
+
const taskClass = classifyPlanTaskClass(goal);
|
|
1247
1307
|
const focus = summarizeGoalForStepTitle(goal);
|
|
1248
1308
|
const summary =
|
|
1249
1309
|
requirements.length > 0
|
|
1250
1310
|
? `Auto fallback plan for: ${requirements.join('; ')}`
|
|
1251
1311
|
: `Auto fallback plan for: ${goal}`;
|
|
1252
1312
|
|
|
1313
|
+
if (taskClass === 'advisory') {
|
|
1314
|
+
return {
|
|
1315
|
+
summary,
|
|
1316
|
+
steps: [
|
|
1317
|
+
{
|
|
1318
|
+
title: 'Inspect the target area',
|
|
1319
|
+
role: 'planner',
|
|
1320
|
+
task: `Inspect the relevant project context for: ${goal}. Identify constraints, evidence, and likely high-value advisory areas.`
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
title: `Advise on ${focus}`,
|
|
1324
|
+
role: 'advisor',
|
|
1325
|
+
task: `Analyze the findings for: ${goal}. Produce prioritized recommendations, tradeoffs, evidence, and open questions without implementing changes.`
|
|
1326
|
+
},
|
|
1327
|
+
buildDefaultSummarizerStep(goal)
|
|
1328
|
+
]
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1253
1332
|
if (lightweightGoal) {
|
|
1333
|
+
const summarizerStep = buildDefaultSummarizerStep(goal);
|
|
1254
1334
|
return {
|
|
1255
1335
|
summary,
|
|
1256
1336
|
steps: [
|
|
@@ -1263,7 +1343,8 @@ function buildFallbackAutoPlan(goal) {
|
|
|
1263
1343
|
title: 'Verify the change',
|
|
1264
1344
|
role: 'tester',
|
|
1265
1345
|
task: `Verify the completed change for: ${goal}. Run the most relevant focused checks available and report concrete evidence plus anything still unverified.`
|
|
1266
|
-
}
|
|
1346
|
+
},
|
|
1347
|
+
summarizerStep
|
|
1267
1348
|
]
|
|
1268
1349
|
};
|
|
1269
1350
|
}
|
|
@@ -1308,6 +1389,13 @@ function buildFallbackAutoPlan(goal) {
|
|
|
1308
1389
|
function buildDefaultSummarizerStep(goal, source = []) {
|
|
1309
1390
|
const existing = (Array.isArray(source) ? source : []).find((step) => step.role === 'summarizer');
|
|
1310
1391
|
if (existing?.title && existing?.task) return existing;
|
|
1392
|
+
if (classifyPlanTaskClass(goal) === 'advisory') {
|
|
1393
|
+
return {
|
|
1394
|
+
title: 'Synthesize final findings',
|
|
1395
|
+
role: 'summarizer',
|
|
1396
|
+
task: `Synthesize the advisory findings for: ${goal}. Read the accumulated observations, recommendations, tradeoffs, evidence, and open questions from earlier steps, then produce a concise final summary with the single best next action.`
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1311
1399
|
return {
|
|
1312
1400
|
title: 'Synthesize final implementation status',
|
|
1313
1401
|
role: 'summarizer',
|
|
@@ -1320,7 +1408,7 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
1320
1408
|
const requirements = deriveGoalRequirements(goal);
|
|
1321
1409
|
const lightweightGoal = isLightweightAutoPlanGoal(goal, requirements);
|
|
1322
1410
|
const taskClass = classifyPlanTaskClass(goal);
|
|
1323
|
-
const implementationSteps = source.filter((step) => step.role !== 'reviewer' && step.role !== 'tester' && step.role !== 'summarizer');
|
|
1411
|
+
const implementationSteps = source.filter((step) => step.role !== 'advisor' && step.role !== 'reviewer' && step.role !== 'tester' && step.role !== 'summarizer');
|
|
1324
1412
|
const primaryImplementationStep =
|
|
1325
1413
|
implementationSteps.find((step) => step.role === 'coder') ||
|
|
1326
1414
|
implementationSteps[0] || {
|
|
@@ -1343,17 +1431,49 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
1343
1431
|
const hasTester = source.some((step) => step.role === 'tester');
|
|
1344
1432
|
|
|
1345
1433
|
if (taskClass === 'advisory') {
|
|
1346
|
-
const advisorySteps = source
|
|
1434
|
+
const advisorySteps = source
|
|
1435
|
+
.filter((step) => step.role === 'planner' || step.role === 'advisor' || step.role === 'coder')
|
|
1436
|
+
.map((step) =>
|
|
1437
|
+
step.role === 'coder'
|
|
1438
|
+
? {
|
|
1439
|
+
...step,
|
|
1440
|
+
role: 'advisor',
|
|
1441
|
+
title: /^implement\b/i.test(String(step.title || '')) ? 'Advise on requested goal' : step.title
|
|
1442
|
+
}
|
|
1443
|
+
: step
|
|
1444
|
+
);
|
|
1445
|
+
const hasAdvisor = advisorySteps.some((step) => step.role === 'advisor');
|
|
1446
|
+
const baseSteps =
|
|
1447
|
+
advisorySteps.length > 0
|
|
1448
|
+
? advisorySteps.slice(0, 6)
|
|
1449
|
+
: [
|
|
1450
|
+
{
|
|
1451
|
+
title: 'Advise on requested goal',
|
|
1452
|
+
role: 'advisor',
|
|
1453
|
+
task: `Analyze the goal and recommend the highest-value next steps for: ${goal}`
|
|
1454
|
+
}
|
|
1455
|
+
];
|
|
1456
|
+
const finalSteps = hasAdvisor
|
|
1457
|
+
? baseSteps
|
|
1458
|
+
: [
|
|
1459
|
+
...baseSteps,
|
|
1460
|
+
{
|
|
1461
|
+
title: 'Advise on requested goal',
|
|
1462
|
+
role: 'advisor',
|
|
1463
|
+
task: `Analyze the goal and recommend the highest-value next steps for: ${goal}`
|
|
1464
|
+
}
|
|
1465
|
+
];
|
|
1347
1466
|
return {
|
|
1348
1467
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
1349
|
-
steps:
|
|
1468
|
+
steps: [...finalSteps, summarizerStep]
|
|
1350
1469
|
};
|
|
1351
1470
|
}
|
|
1352
1471
|
|
|
1353
1472
|
if (lightweightGoal) {
|
|
1473
|
+
const baseSteps = hasTester ? [primaryImplementationStep, testerStep] : [primaryImplementationStep];
|
|
1354
1474
|
return {
|
|
1355
1475
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
1356
|
-
steps:
|
|
1476
|
+
steps: [...baseSteps, summarizerStep]
|
|
1357
1477
|
};
|
|
1358
1478
|
}
|
|
1359
1479
|
|
|
@@ -1362,11 +1482,9 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
1362
1482
|
...(hasReviewer ? [reviewerStep] : []),
|
|
1363
1483
|
...(testerStep ? [testerStep] : [])
|
|
1364
1484
|
];
|
|
1365
|
-
const needsSummarizer = executionSteps.length >= 3;
|
|
1366
|
-
|
|
1367
1485
|
return {
|
|
1368
1486
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
1369
|
-
steps:
|
|
1487
|
+
steps: [...executionSteps, summarizerStep]
|
|
1370
1488
|
};
|
|
1371
1489
|
}
|
|
1372
1490
|
|
|
@@ -1618,7 +1736,7 @@ async function buildAutoPlanFinalSummary({
|
|
|
1618
1736
|
content: buildAutoPlanFinalSummaryUserPrompt({ goal, autoPlan, runItems, planningError })
|
|
1619
1737
|
}
|
|
1620
1738
|
],
|
|
1621
|
-
timeoutMs: config.gateway.timeout_ms ||
|
|
1739
|
+
timeoutMs: config.gateway.timeout_ms || 1800000,
|
|
1622
1740
|
maxRetries: config.gateway.max_retries ?? 2
|
|
1623
1741
|
});
|
|
1624
1742
|
return trimInline(result.text || '', 600) || fallbackSummary;
|
|
@@ -1656,35 +1774,51 @@ async function removePlanFileIfPresent(planState) {
|
|
|
1656
1774
|
|
|
1657
1775
|
function buildSpecTemplate(topic) {
|
|
1658
1776
|
return `
|
|
1659
|
-
#
|
|
1777
|
+
# ${topic} Design
|
|
1660
1778
|
|
|
1661
|
-
##
|
|
1662
|
-
-
|
|
1663
|
-
-
|
|
1779
|
+
## Summary
|
|
1780
|
+
- Problem statement
|
|
1781
|
+
- Desired outcome
|
|
1782
|
+
- Why this is worth doing
|
|
1664
1783
|
|
|
1665
|
-
##
|
|
1784
|
+
## Goals
|
|
1666
1785
|
- Primary goal
|
|
1667
|
-
-
|
|
1786
|
+
- Secondary goals
|
|
1787
|
+
|
|
1788
|
+
## Non-Goals
|
|
1789
|
+
- Out-of-scope behavior
|
|
1790
|
+
- Explicitly rejected approaches
|
|
1668
1791
|
|
|
1669
|
-
##
|
|
1670
|
-
-
|
|
1671
|
-
-
|
|
1792
|
+
## User Experience / Command Behavior
|
|
1793
|
+
- User-facing commands or flows
|
|
1794
|
+
- Review or approval behavior
|
|
1795
|
+
- Expected outputs
|
|
1672
1796
|
|
|
1673
|
-
##
|
|
1797
|
+
## Architecture
|
|
1798
|
+
- Main modules and responsibilities
|
|
1799
|
+
- Data flow
|
|
1800
|
+
- Integration points
|
|
1801
|
+
|
|
1802
|
+
## Data / State Model
|
|
1803
|
+
- New or changed state
|
|
1804
|
+
- Persistence locations
|
|
1805
|
+
- Lifecycle and cleanup behavior
|
|
1806
|
+
|
|
1807
|
+
## Safety Rules
|
|
1808
|
+
- Guardrails
|
|
1809
|
+
- Permission or approval requirements
|
|
1810
|
+
- Failure behavior
|
|
1811
|
+
|
|
1812
|
+
## Requirements
|
|
1674
1813
|
- Functional requirements
|
|
1675
1814
|
- Non-functional requirements
|
|
1676
1815
|
- Win10 compatibility requirements
|
|
1677
1816
|
|
|
1678
|
-
##
|
|
1679
|
-
- Architecture sketch
|
|
1680
|
-
- Data flow
|
|
1681
|
-
- Key interfaces/commands
|
|
1682
|
-
|
|
1683
|
-
## 6. Risks and Mitigations
|
|
1817
|
+
## Risks and Mitigations
|
|
1684
1818
|
- Risk
|
|
1685
1819
|
- Mitigation
|
|
1686
1820
|
|
|
1687
|
-
##
|
|
1821
|
+
## Testing / Validation
|
|
1688
1822
|
- Test strategy
|
|
1689
1823
|
- Acceptance checklist
|
|
1690
1824
|
`;
|
|
@@ -1703,17 +1837,20 @@ async function buildSpecWithModel({
|
|
|
1703
1837
|
systemPrompt
|
|
1704
1838
|
}) {
|
|
1705
1839
|
const prompt = [
|
|
1706
|
-
'Write a practical engineering spec in markdown.',
|
|
1840
|
+
'Write a practical engineering spec in markdown, like an implementation-ready design document.',
|
|
1707
1841
|
'Use these sections exactly:',
|
|
1708
|
-
'#
|
|
1709
|
-
'##
|
|
1710
|
-
'##
|
|
1711
|
-
'##
|
|
1712
|
-
'##
|
|
1713
|
-
'##
|
|
1714
|
-
'##
|
|
1715
|
-
'##
|
|
1716
|
-
'
|
|
1842
|
+
'# <Feature> Design',
|
|
1843
|
+
'## Summary',
|
|
1844
|
+
'## Goals',
|
|
1845
|
+
'## Non-Goals',
|
|
1846
|
+
'## User Experience / Command Behavior',
|
|
1847
|
+
'## Architecture',
|
|
1848
|
+
'## Data / State Model',
|
|
1849
|
+
'## Safety Rules',
|
|
1850
|
+
'## Requirements',
|
|
1851
|
+
'## Risks and Mitigations',
|
|
1852
|
+
'## Testing / Validation',
|
|
1853
|
+
'Make it concrete, scoped, and suitable for turning into a sub-agent implementation plan.'
|
|
1717
1854
|
].join('\n');
|
|
1718
1855
|
|
|
1719
1856
|
const result = await createChatCompletion({
|
|
@@ -1725,7 +1862,7 @@ async function buildSpecWithModel({
|
|
|
1725
1862
|
{ role: 'system', content: `${systemPrompt}\n${prompt}` },
|
|
1726
1863
|
{ role: 'user', content: `Topic: ${topic}` }
|
|
1727
1864
|
],
|
|
1728
|
-
timeoutMs: config.gateway.timeout_ms ||
|
|
1865
|
+
timeoutMs: config.gateway.timeout_ms || 1800000,
|
|
1729
1866
|
maxRetries: config.gateway.max_retries ?? 2
|
|
1730
1867
|
});
|
|
1731
1868
|
return String(result.text || '').trim();
|
|
@@ -1788,7 +1925,7 @@ async function buildPlanFromSpecWithModel({
|
|
|
1788
1925
|
content: `Spec path: ${specPath || '(inline)'}\n\nProject implementation constraints:\n${projectConstraints}\n\n${specText}`
|
|
1789
1926
|
}
|
|
1790
1927
|
],
|
|
1791
|
-
timeoutMs: config.gateway.timeout_ms ||
|
|
1928
|
+
timeoutMs: config.gateway.timeout_ms || 1800000,
|
|
1792
1929
|
maxRetries: config.gateway.max_retries ?? 2
|
|
1793
1930
|
});
|
|
1794
1931
|
return String(result.text || '').trim();
|
|
@@ -1886,6 +2023,72 @@ function effectiveMaxContextTokens(config) {
|
|
|
1886
2023
|
return 32000;
|
|
1887
2024
|
}
|
|
1888
2025
|
|
|
2026
|
+
function estimateTextTokens(role, content) {
|
|
2027
|
+
const text = String(content || '');
|
|
2028
|
+
if (!text) return 0;
|
|
2029
|
+
return estimateMessagesTokens([{ role, content: text }]);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function makePromptBudgetComponent(name, role, content) {
|
|
2033
|
+
const text = String(content || '');
|
|
2034
|
+
return {
|
|
2035
|
+
name,
|
|
2036
|
+
chars: text.length,
|
|
2037
|
+
estimated_tokens: estimateTextTokens(role, text)
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
function buildPromptBudgetAudit({
|
|
2042
|
+
systemPrompt = '',
|
|
2043
|
+
projectContextSnippet = '',
|
|
2044
|
+
projectContextGuidance = '',
|
|
2045
|
+
messages = [],
|
|
2046
|
+
toolDefinitions = [],
|
|
2047
|
+
config = {}
|
|
2048
|
+
}) {
|
|
2049
|
+
const toolSchemaText = JSON.stringify(toolDefinitions || []);
|
|
2050
|
+
const messageTexts = (Array.isArray(messages) ? messages : []).map((message) => ({
|
|
2051
|
+
role: message?.role || 'user',
|
|
2052
|
+
content: message?.content || ''
|
|
2053
|
+
}));
|
|
2054
|
+
const components = [
|
|
2055
|
+
makePromptBudgetComponent('system_prompt', 'system', systemPrompt),
|
|
2056
|
+
makePromptBudgetComponent('project_context', 'system', projectContextSnippet),
|
|
2057
|
+
makePromptBudgetComponent('project_context_guidance', 'system', projectContextGuidance),
|
|
2058
|
+
{
|
|
2059
|
+
name: 'message_history',
|
|
2060
|
+
chars: messageTexts.reduce((total, message) => total + String(message.content || '').length, 0),
|
|
2061
|
+
estimated_tokens: estimateMessagesTokens(messageTexts)
|
|
2062
|
+
},
|
|
2063
|
+
makePromptBudgetComponent('tool_schemas', 'system', toolSchemaText)
|
|
2064
|
+
];
|
|
2065
|
+
const totalChars = components.reduce((total, component) => total + component.chars, 0);
|
|
2066
|
+
const totalTokens = components.reduce((total, component) => total + component.estimated_tokens, 0);
|
|
2067
|
+
const maxContextTokens = effectiveMaxContextTokens(config);
|
|
2068
|
+
const contextUsagePct =
|
|
2069
|
+
maxContextTokens > 0 ? Math.min(100, Math.max(0, (totalTokens / maxContextTokens) * 100)) : 0;
|
|
2070
|
+
return {
|
|
2071
|
+
components,
|
|
2072
|
+
total: {
|
|
2073
|
+
chars: totalChars,
|
|
2074
|
+
estimated_tokens: totalTokens
|
|
2075
|
+
},
|
|
2076
|
+
max_context_tokens: maxContextTokens,
|
|
2077
|
+
context_usage_pct: contextUsagePct
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
function summarizePromptBudgetAudit(audit) {
|
|
2082
|
+
const totalTokens = audit?.total?.estimated_tokens || 0;
|
|
2083
|
+
const maxContextTokens = audit?.max_context_tokens || 0;
|
|
2084
|
+
const pct = Number(audit?.context_usage_pct || 0).toFixed(1);
|
|
2085
|
+
const components = (audit?.components || [])
|
|
2086
|
+
.filter((component) => component.estimated_tokens > 0)
|
|
2087
|
+
.map((component) => `${component.name}=${component.estimated_tokens}`)
|
|
2088
|
+
.join(', ');
|
|
2089
|
+
return `prompt budget: ${totalTokens}/${maxContextTokens} est tokens (${pct}%)${components ? `; ${components}` : ''}`;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
1889
2092
|
function buildRuntimeStateSnapshot({ currentSession, config, model, executionMode, extraSession }) {
|
|
1890
2093
|
const parentTokens = estimateMessagesTokens(currentSession?.messages || []);
|
|
1891
2094
|
const subTokens = extraSession ? estimateMessagesTokens(extraSession.messages || []) : 0;
|
|
@@ -1896,6 +2099,7 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
1896
2099
|
sessionId: currentSession?.id || '',
|
|
1897
2100
|
mode: executionMode || config.execution?.mode || 'auto',
|
|
1898
2101
|
sdkProvider: config.sdk?.provider || 'openai-compatible',
|
|
2102
|
+
agentRole: 'general',
|
|
1899
2103
|
model: model || config.model?.name || '',
|
|
1900
2104
|
maxContextTokens
|
|
1901
2105
|
};
|
|
@@ -1914,6 +2118,11 @@ function buildRuntimeStateSnapshot({ currentSession, config, model, executionMod
|
|
|
1914
2118
|
value: currentSession?.planState?.status === 'pending_approval',
|
|
1915
2119
|
enumerable: false,
|
|
1916
2120
|
writable: false
|
|
2121
|
+
},
|
|
2122
|
+
pendingReflectSkill: {
|
|
2123
|
+
value: currentSession?.planState?.status === 'pending_reflect_skill',
|
|
2124
|
+
enumerable: false,
|
|
2125
|
+
writable: false
|
|
1917
2126
|
}
|
|
1918
2127
|
});
|
|
1919
2128
|
return snapshot;
|
|
@@ -1940,6 +2149,10 @@ function hasPendingPlanApproval(session) {
|
|
|
1940
2149
|
return session?.planState?.status === 'pending_approval';
|
|
1941
2150
|
}
|
|
1942
2151
|
|
|
2152
|
+
function hasPendingReflectSkill(session) {
|
|
2153
|
+
return session?.planState?.status === 'pending_reflect_skill';
|
|
2154
|
+
}
|
|
2155
|
+
|
|
1943
2156
|
function isApprovalText(text = '') {
|
|
1944
2157
|
const value = String(text || '').trim().toLowerCase();
|
|
1945
2158
|
if (!value) return false;
|
|
@@ -1976,6 +2189,28 @@ function buildPendingPlanApprovalMessage(planState) {
|
|
|
1976
2189
|
return lines.join('\n');
|
|
1977
2190
|
}
|
|
1978
2191
|
|
|
2192
|
+
function buildPendingReflectSkillMessage(reflectState) {
|
|
2193
|
+
const candidates = Array.isArray(reflectState?.candidates) ? reflectState.candidates : [];
|
|
2194
|
+
if (candidates.length === 0) {
|
|
2195
|
+
return 'Reflect found no reusable skill candidate.';
|
|
2196
|
+
}
|
|
2197
|
+
const lines = [
|
|
2198
|
+
'Reflect skill draft pending.',
|
|
2199
|
+
`Scope: ${reflectState?.targetScope || 'project'}`
|
|
2200
|
+
];
|
|
2201
|
+
for (const candidate of candidates) {
|
|
2202
|
+
lines.push('');
|
|
2203
|
+
lines.push(`[${candidate.id || 1}] ${candidate.name}`);
|
|
2204
|
+
lines.push(`Confidence: ${Number(candidate.confidence ?? 0.75).toFixed(2)}`);
|
|
2205
|
+
lines.push(`Target: ${candidate.targetPath || '-'}`);
|
|
2206
|
+
lines.push('');
|
|
2207
|
+
lines.push(String(candidate.content || '').trim());
|
|
2208
|
+
}
|
|
2209
|
+
lines.push('');
|
|
2210
|
+
lines.push('Use /yes to write this skill, /edit <feedback> to revise it, or /no to discard it.');
|
|
2211
|
+
return lines.join('\n');
|
|
2212
|
+
}
|
|
2213
|
+
|
|
1979
2214
|
function buildApprovedPlanExecutionPrompt(planState, approvalText = '') {
|
|
1980
2215
|
const requirementPacket = buildGoalRequirementPacket(planState?.goal || '', 'coder');
|
|
1981
2216
|
const lines = [
|
|
@@ -2167,8 +2402,10 @@ async function askModel({
|
|
|
2167
2402
|
}
|
|
2168
2403
|
|
|
2169
2404
|
const projectContextSnippet = await buildProjectContextSnippet(process.cwd(), text).catch(() => '');
|
|
2405
|
+
const projectContextGuidance =
|
|
2406
|
+
'Use this project context as lightweight guidance and verify important details with fresh reads when needed.';
|
|
2170
2407
|
const effectiveSystemPrompt = projectContextSnippet
|
|
2171
|
-
? `${systemPrompt}\n\n${projectContextSnippet}\n\
|
|
2408
|
+
? `${systemPrompt}\n\n${projectContextSnippet}\n\n${projectContextGuidance}`
|
|
2172
2409
|
: systemPrompt;
|
|
2173
2410
|
|
|
2174
2411
|
const { definitions, handlers, formatters, deferredDefinitions, dispose: disposeTools } = getBuiltinTools({
|
|
@@ -2198,6 +2435,31 @@ async function askModel({
|
|
|
2198
2435
|
? Object.fromEntries(Object.entries(deferredDefinitions).filter(([name]) => allowedTools.includes(name)))
|
|
2199
2436
|
: deferredDefinitions;
|
|
2200
2437
|
|
|
2438
|
+
if (config.context?.prompt_budget_audit === true && onAgentEvent) {
|
|
2439
|
+
const auditId = `prompt-budget-${Date.now()}`;
|
|
2440
|
+
const audit = buildPromptBudgetAudit({
|
|
2441
|
+
systemPrompt,
|
|
2442
|
+
projectContextSnippet,
|
|
2443
|
+
projectContextGuidance: projectContextSnippet ? projectContextGuidance : '',
|
|
2444
|
+
messages: session.messages.filter((m) => m.role !== 'system'),
|
|
2445
|
+
toolDefinitions: filteredDefinitions,
|
|
2446
|
+
config
|
|
2447
|
+
});
|
|
2448
|
+
onAgentEvent({
|
|
2449
|
+
type: 'system_tool:start',
|
|
2450
|
+
id: auditId,
|
|
2451
|
+
name: 'prompt_budget',
|
|
2452
|
+
summary: 'calculating prompt budget'
|
|
2453
|
+
});
|
|
2454
|
+
onAgentEvent({
|
|
2455
|
+
type: 'system_tool:end',
|
|
2456
|
+
id: auditId,
|
|
2457
|
+
name: 'prompt_budget',
|
|
2458
|
+
summary: summarizePromptBudgetAudit(audit),
|
|
2459
|
+
details: audit
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2201
2463
|
let activeAssistantIndex = -1;
|
|
2202
2464
|
const wrappedAgentEvent = (event) => {
|
|
2203
2465
|
// Always accumulate messages in session (for token tracking), only save when persisting
|
|
@@ -2277,7 +2539,7 @@ async function askModel({
|
|
|
2277
2539
|
model: selectedModel,
|
|
2278
2540
|
messages,
|
|
2279
2541
|
tools,
|
|
2280
|
-
timeoutMs: config.gateway.timeout_ms ||
|
|
2542
|
+
timeoutMs: config.gateway.timeout_ms || 1800000,
|
|
2281
2543
|
maxRetries: config.gateway.max_retries ?? 2,
|
|
2282
2544
|
signal,
|
|
2283
2545
|
onTextDelta: (delta) => {
|
|
@@ -2375,6 +2637,12 @@ async function runSubAgentTask({
|
|
|
2375
2637
|
}
|
|
2376
2638
|
} catch {}
|
|
2377
2639
|
}
|
|
2640
|
+
if (
|
|
2641
|
+
role !== 'summarizer' &&
|
|
2642
|
+
['assistant:start', 'assistant:delta', 'assistant:response', 'assistant:tool_call_delta'].includes(String(evt?.type || ''))
|
|
2643
|
+
) {
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2378
2646
|
if (onAgentEvent) onAgentEvent(evt);
|
|
2379
2647
|
};
|
|
2380
2648
|
const roleAllowedTools = ROLE_TOOL_POLICY[role];
|
|
@@ -2503,7 +2771,14 @@ async function executePlanWithSubAgents({
|
|
|
2503
2771
|
);
|
|
2504
2772
|
}
|
|
2505
2773
|
|
|
2506
|
-
if (stepRecord.failed && i < steps.length - 1)
|
|
2774
|
+
if (stepRecord.failed && i < steps.length - 1) {
|
|
2775
|
+
const summarizerIndex = steps.findIndex((candidate, index) => index > i && candidate.role === 'summarizer');
|
|
2776
|
+
if (summarizerIndex > i) {
|
|
2777
|
+
i = summarizerIndex - 1;
|
|
2778
|
+
continue;
|
|
2779
|
+
}
|
|
2780
|
+
break;
|
|
2781
|
+
}
|
|
2507
2782
|
}
|
|
2508
2783
|
|
|
2509
2784
|
const summaryLines = [];
|
|
@@ -2557,14 +2832,15 @@ async function buildAutoPlanAndRun({
|
|
|
2557
2832
|
'- advisory = analysis, review, audit, optimization suggestions, architecture feedback, brainstorming, planning, or recommendation requests.',
|
|
2558
2833
|
'- implementation = add/build/create/implement/refactor/fix/update/change behavior in code or files.',
|
|
2559
2834
|
'- verification-heavy = the user explicitly asks to run tests, verify findings, reproduce a bug, prove a claim, or validate a result.',
|
|
2560
|
-
'- For advisory goals, prefer
|
|
2835
|
+
'- For advisory goals, prefer planner and advisor roles. Do not use coder unless the plan will actually modify code or files.',
|
|
2836
|
+
'- For advisory goals, do not use reviewer or tester unless the user explicitly asks for verification or review as a separate deliverable.',
|
|
2561
2837
|
'- For advisory goals, do not emit generic filler steps such as "Test and verify", "Review recommendations", or other template-only steps.',
|
|
2562
2838
|
'- For implementation goals, reviewer and tester are optional support roles, not defaults. Only include them when they clearly add value.',
|
|
2563
2839
|
'- 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.',
|
|
2564
2840
|
'- If the task is purely to inspect the current project and suggest improvements, a lean 2-step or 3-step plan is preferred.',
|
|
2565
|
-
'- Example advisory roles: planner -> inspect project shape,
|
|
2841
|
+
'- Example advisory roles: planner -> inspect project shape, advisor -> synthesize findings and prioritized recommendations.',
|
|
2566
2842
|
'- Example implementation roles: planner -> inspect target area, coder -> implement change, tester -> verify changed behavior.',
|
|
2567
|
-
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester|summarizer","task":"..."}]}. No markdown.'
|
|
2843
|
+
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|advisor|coder|reviewer|tester|summarizer","task":"..."}]}. No markdown.'
|
|
2568
2844
|
].join('\n');
|
|
2569
2845
|
let autoPlan = {
|
|
2570
2846
|
summary: `Auto plan for: ${goal}`,
|
|
@@ -2589,14 +2865,15 @@ async function buildAutoPlanAndRun({
|
|
|
2589
2865
|
role: 'user',
|
|
2590
2866
|
content: [
|
|
2591
2867
|
'Create an execution plan and assign best sub-agent role for each step.',
|
|
2592
|
-
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester|summarizer","task":"..."}]}. No markdown.',
|
|
2593
|
-
'The available roles are planner, coder, reviewer, tester, and summarizer. Use only the roles the task actually needs.',
|
|
2594
|
-
'The summarizer
|
|
2868
|
+
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|advisor|coder|reviewer|tester|summarizer","task":"..."}]}. No markdown.',
|
|
2869
|
+
'The available roles are planner, advisor, coder, reviewer, tester, and summarizer. Use only the non-summary roles the task actually needs.',
|
|
2870
|
+
'Always include a summarizer as the final step. The summarizer synthesizes prior step results without re-analyzing.',
|
|
2871
|
+
'Planner, advisor, coder, reviewer, and tester steps should write detailed step results, not final summaries.',
|
|
2595
2872
|
`Task class: ${normalizedTaskClass}`,
|
|
2596
2873
|
'Before choosing roles, decide whether the request is advisory, implementation, or verification-heavy.',
|
|
2597
2874
|
requirementPacket,
|
|
2598
2875
|
'The first step should usually inspect or clarify the target area before implementation.',
|
|
2599
|
-
'For analysis, recommendation, optimization, audit, or project-review goals, keep the plan lean and usually limit it to planner/
|
|
2876
|
+
'For analysis, recommendation, optimization, audit, or project-review goals, keep the plan lean and usually limit it to planner/advisor.',
|
|
2600
2877
|
'Do not include reviewer/tester for advisory goals unless the user explicitly asks to validate, verify, or independently review the findings.',
|
|
2601
2878
|
'Avoid template-only titles like "Initial analysis", "Review recommendations", or "Test and verify" for advisory goals.',
|
|
2602
2879
|
'For implementation-heavy changes, prefer review and/or testing steps near the end only when they materially improve confidence.',
|
|
@@ -2606,7 +2883,7 @@ async function buildAutoPlanAndRun({
|
|
|
2606
2883
|
.join('\n')
|
|
2607
2884
|
}
|
|
2608
2885
|
],
|
|
2609
|
-
timeoutMs: config.gateway.timeout_ms ||
|
|
2886
|
+
timeoutMs: config.gateway.timeout_ms || 1800000,
|
|
2610
2887
|
maxRetries: config.gateway.max_retries ?? 2
|
|
2611
2888
|
});
|
|
2612
2889
|
const parsed = extractJsonBlock(planning.text || '');
|
|
@@ -2707,8 +2984,9 @@ async function revisePendingPlanWithModel({
|
|
|
2707
2984
|
const prompt = [
|
|
2708
2985
|
buildAutoPlanPlannerGuidance(),
|
|
2709
2986
|
'You are revising an existing plan based on explicit user feedback.',
|
|
2710
|
-
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester|summarizer","task":"..."}]}. No markdown.',
|
|
2711
|
-
'Keep roles minimal and only include steps that materially help the goal.'
|
|
2987
|
+
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|advisor|coder|reviewer|tester|summarizer","task":"..."}]}. No markdown.',
|
|
2988
|
+
'Keep roles minimal and only include steps that materially help the goal.',
|
|
2989
|
+
'Always keep a summarizer as the final step.'
|
|
2712
2990
|
].join('\n');
|
|
2713
2991
|
const result = await createChatCompletion({
|
|
2714
2992
|
sdkProvider: config.sdk?.provider,
|
|
@@ -2730,7 +3008,7 @@ async function revisePendingPlanWithModel({
|
|
|
2730
3008
|
].join('\n')
|
|
2731
3009
|
}
|
|
2732
3010
|
],
|
|
2733
|
-
timeoutMs: config.gateway.timeout_ms ||
|
|
3011
|
+
timeoutMs: config.gateway.timeout_ms || 1800000,
|
|
2734
3012
|
maxRetries: config.gateway.max_retries ?? 2
|
|
2735
3013
|
});
|
|
2736
3014
|
const parsed = extractJsonBlock(result.text || '');
|
|
@@ -2782,6 +3060,44 @@ async function handleShellInput(shellText, config) {
|
|
|
2782
3060
|
return { text: chunks.join('\n') };
|
|
2783
3061
|
}
|
|
2784
3062
|
|
|
3063
|
+
function formatHistoryTimestamp(value) {
|
|
3064
|
+
const raw = String(value || '').trim();
|
|
3065
|
+
if (!raw) return 'updated unknown';
|
|
3066
|
+
const parsed = new Date(raw);
|
|
3067
|
+
if (Number.isNaN(parsed.getTime())) return `updated ${raw}`;
|
|
3068
|
+
return `updated ${parsed.toISOString().slice(0, 16).replace('T', ' ')}`;
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
function compactHistoryPreview(value, maxChars = 72) {
|
|
3072
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
3073
|
+
if (!text) return '(no preview)';
|
|
3074
|
+
if (text.length <= maxChars) return text;
|
|
3075
|
+
return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
function formatHistoryList({ currentSession, sessions }) {
|
|
3079
|
+
const currentMessages = Array.isArray(currentSession?.messages) ? currentSession.messages.length : 0;
|
|
3080
|
+
const lines = [
|
|
3081
|
+
`Current session ${currentSession.id}`,
|
|
3082
|
+
`Messages ${currentMessages}`,
|
|
3083
|
+
'',
|
|
3084
|
+
'Recent sessions'
|
|
3085
|
+
];
|
|
3086
|
+
|
|
3087
|
+
for (const [index, session] of sessions.entries()) {
|
|
3088
|
+
const count = Number(session.messageCount || 0);
|
|
3089
|
+
lines.push(
|
|
3090
|
+
`${index + 1}. ${session.id}`,
|
|
3091
|
+
` ${count} ${count === 1 ? 'msg' : 'msgs'} | ${formatHistoryTimestamp(session.updatedAt)}`,
|
|
3092
|
+
` ${compactHistoryPreview(session.preview)}`,
|
|
3093
|
+
` resume: /history resume ${session.id}`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
lines.push('', 'Tip: use /history resume <session_id>');
|
|
3098
|
+
return lines.join('\n');
|
|
3099
|
+
}
|
|
3100
|
+
|
|
2785
3101
|
export async function createChatRuntime({
|
|
2786
3102
|
session,
|
|
2787
3103
|
config: initialConfig,
|
|
@@ -2830,6 +3146,13 @@ export async function createChatRuntime({
|
|
|
2830
3146
|
executionMode = 'plan';
|
|
2831
3147
|
}
|
|
2832
3148
|
const commands = await loadCommandsAndSkills();
|
|
3149
|
+
const reloadCommandsAndSkills = async () => {
|
|
3150
|
+
const next = await loadCommandsAndSkills();
|
|
3151
|
+
commands.clear();
|
|
3152
|
+
for (const [name, command] of next.entries()) {
|
|
3153
|
+
commands.set(name, command);
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
2833
3156
|
|
|
2834
3157
|
// Set up tool result store under session directory
|
|
2835
3158
|
const sessionResultsDir = path.join(getSessionsDir(), String(currentSession.id));
|
|
@@ -2940,6 +3263,7 @@ export async function createChatRuntime({
|
|
|
2940
3263
|
{ name: 'config', description: completionCopy.commands.config },
|
|
2941
3264
|
{ name: 'memory', description: completionCopy.commands.memory },
|
|
2942
3265
|
{ name: 'dream', description: completionCopy.commands.dream },
|
|
3266
|
+
{ name: 'reflect', description: completionCopy.commands.reflect },
|
|
2943
3267
|
{ name: 'history', description: completionCopy.commands.history },
|
|
2944
3268
|
{ name: 'debug', description: completionCopy.commands.debug },
|
|
2945
3269
|
{ name: 'retry', description: completionCopy.commands.retry },
|
|
@@ -2948,7 +3272,7 @@ export async function createChatRuntime({
|
|
|
2948
3272
|
];
|
|
2949
3273
|
const out = [];
|
|
2950
3274
|
for (const cmd of commands.values()) {
|
|
2951
|
-
if (cmd.metadata.type === 'skill' && config
|
|
3275
|
+
if (cmd.metadata.type === 'skill' && !isSkillEnabled(config, cmd.name, cmd)) {
|
|
2952
3276
|
continue;
|
|
2953
3277
|
}
|
|
2954
3278
|
out.push({
|
|
@@ -2988,9 +3312,10 @@ export async function createChatRuntime({
|
|
|
2988
3312
|
];
|
|
2989
3313
|
const specTemplates = ['/spec <topic>'];
|
|
2990
3314
|
const planTemplates = ['/plan <goal>', '/plan auto <goal>', '/plan approve', '/plan from-spec <spec-path?>'];
|
|
2991
|
-
const agentTemplates = ['/agents list', '/agents run planner <task>', '/agents run coder <task>', '/agents run reviewer <task>', '/agents run tester <task>', '/agents run summarizer <task>'];
|
|
3315
|
+
const agentTemplates = ['/agents list', '/agents run planner <task>', '/agents run advisor <task>', '/agents run coder <task>', '/agents run reviewer <task>', '/agents run tester <task>', '/agents run summarizer <task>'];
|
|
2992
3316
|
const debugTemplates = ['/debug keys on', '/debug keys off', '/debug keys status'];
|
|
2993
3317
|
const dreamTemplates = ['/dream', '/dream --dry-run', '/dream --scope=project', '/dream --scope=global'];
|
|
3318
|
+
const reflectTemplates = ['/reflect', '/reflect --scope=global <request>', '/reflect <request>'];
|
|
2994
3319
|
const compactTemplates = compactOptions.map((opt) => `/compact ${opt}`);
|
|
2995
3320
|
const slashTemplates = [
|
|
2996
3321
|
...configTemplates,
|
|
@@ -3003,6 +3328,7 @@ export async function createChatRuntime({
|
|
|
3003
3328
|
...agentTemplates,
|
|
3004
3329
|
...debugTemplates,
|
|
3005
3330
|
...dreamTemplates,
|
|
3331
|
+
...reflectTemplates,
|
|
3006
3332
|
...compactTemplates,
|
|
3007
3333
|
'/retry',
|
|
3008
3334
|
'/status'
|
|
@@ -3070,6 +3396,7 @@ export async function createChatRuntime({
|
|
|
3070
3396
|
for (const template of agentTemplates) registerSuggestion(template, completionCopy.generic.agentCommand);
|
|
3071
3397
|
for (const template of debugTemplates) registerSuggestion(template, completionCopy.generic.debugCommand);
|
|
3072
3398
|
for (const template of dreamTemplates) registerSuggestion(template, completionCopy.generic.dreamCommand);
|
|
3399
|
+
for (const template of reflectTemplates) registerSuggestion(template, completionCopy.generic.reflectCommand);
|
|
3073
3400
|
for (const template of compactTemplates) registerSuggestion(template, completionCopy.generic.compactCommand);
|
|
3074
3401
|
registerSuggestion('/retry', completionCopy.generic.retryCommand);
|
|
3075
3402
|
registerSuggestion('/status', completionCopy.generic.statusCommand);
|
|
@@ -3215,7 +3542,7 @@ export async function createChatRuntime({
|
|
|
3215
3542
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
3216
3543
|
const sub = tokens[1] || '';
|
|
3217
3544
|
if (sub === 'run') {
|
|
3218
|
-
return
|
|
3545
|
+
return SUB_AGENT_ROLES
|
|
3219
3546
|
.map((r) => registerSuggestion(`/agents run ${r} `, completionCopy.generic.agentCommand));
|
|
3220
3547
|
}
|
|
3221
3548
|
return ['list', 'run']
|
|
@@ -3224,7 +3551,7 @@ export async function createChatRuntime({
|
|
|
3224
3551
|
}
|
|
3225
3552
|
if (tokens[1] === 'run') {
|
|
3226
3553
|
const rolePrefix = tokens[2] || '';
|
|
3227
|
-
return
|
|
3554
|
+
return SUB_AGENT_ROLES
|
|
3228
3555
|
.filter((r) => r.startsWith(rolePrefix))
|
|
3229
3556
|
.map((r) => registerSuggestion(`/agents run ${r} `, completionCopy.generic.agentCommand));
|
|
3230
3557
|
}
|
|
@@ -3355,6 +3682,27 @@ export async function createChatRuntime({
|
|
|
3355
3682
|
const saveDirectMemoryPrompt = async (text) => {
|
|
3356
3683
|
const direct = classifyDirectMemoryPrompt(text);
|
|
3357
3684
|
if (!direct) return null;
|
|
3685
|
+
const existing = await listMemories({
|
|
3686
|
+
scope: direct.scope,
|
|
3687
|
+
workspaceRoot: process.cwd()
|
|
3688
|
+
}).catch(() => []);
|
|
3689
|
+
const directText = String(direct.content || '').toLowerCase();
|
|
3690
|
+
const directTokens = new Set(directText.match(/[a-z0-9_\u4e00-\u9fa5]+/g) || []);
|
|
3691
|
+
const directAsciiTokens = new Set(directText.match(/[a-z0-9_]{4,}/g) || []);
|
|
3692
|
+
const overlapsExisting = existing.some((item) => {
|
|
3693
|
+
const existingText = `${item.content || ''} ${item.summary || ''}`.toLowerCase();
|
|
3694
|
+
for (const token of directAsciiTokens) {
|
|
3695
|
+
if (existingText.includes(token)) return true;
|
|
3696
|
+
}
|
|
3697
|
+
let hits = 0;
|
|
3698
|
+
for (const token of directTokens) {
|
|
3699
|
+
if (token.length < 2) continue;
|
|
3700
|
+
if (existingText.includes(token)) hits += 1;
|
|
3701
|
+
if (hits >= 2) return true;
|
|
3702
|
+
}
|
|
3703
|
+
return false;
|
|
3704
|
+
});
|
|
3705
|
+
if (overlapsExisting) return null;
|
|
3358
3706
|
return rememberMemory({
|
|
3359
3707
|
scope: direct.scope,
|
|
3360
3708
|
content: direct.content,
|
|
@@ -3495,14 +3843,14 @@ export async function createChatRuntime({
|
|
|
3495
3843
|
if (parsedInput.command === 'help') {
|
|
3496
3844
|
return {
|
|
3497
3845
|
type: 'system',
|
|
3498
|
-
text: 'Commands: /help /exit /new /stop /commands /status /mode /compact /checkpoint /spec /plan /yes /edit /reject /agents /config /memory /capture /inbox /dream /history /debug /retry /<custom> !<shell>'
|
|
3846
|
+
text: 'Commands: /help /exit /new /stop /commands /status /mode /compact /checkpoint /spec /plan /yes /no /edit /reject /agents /config /memory /capture /inbox /dream /reflect /history /debug /retry /<custom> !<shell>'
|
|
3499
3847
|
};
|
|
3500
3848
|
}
|
|
3501
3849
|
if (parsedInput.command === 'status') {
|
|
3502
3850
|
const todoCount = countActiveTodos(currentSession.todos);
|
|
3503
3851
|
return {
|
|
3504
3852
|
type: 'system',
|
|
3505
|
-
text: `mode=${executionMode} | model=${model || config.model.name} | max_ctx=${effectiveMaxContextTokens(config)} | session=${currentSession.id} | todos=${todoCount}`
|
|
3853
|
+
text: `mode=${executionMode} | role=general | model=${model || config.model.name} | max_ctx=${effectiveMaxContextTokens(config)} | session=${currentSession.id} | todos=${todoCount}`
|
|
3506
3854
|
};
|
|
3507
3855
|
}
|
|
3508
3856
|
if (parsedInput.command === 'mode') {
|
|
@@ -3521,6 +3869,27 @@ export async function createChatRuntime({
|
|
|
3521
3869
|
return { type: 'system', text };
|
|
3522
3870
|
}
|
|
3523
3871
|
if (parsedInput.command === 'yes') {
|
|
3872
|
+
if (hasPendingReflectSkill(currentSession)) {
|
|
3873
|
+
const state = { ...currentSession.planState };
|
|
3874
|
+
const candidate = Array.isArray(state.candidates) ? state.candidates[0] : null;
|
|
3875
|
+
if (!candidate) {
|
|
3876
|
+
currentSession.planState = null;
|
|
3877
|
+
const text = 'No reflect skill draft to write.';
|
|
3878
|
+
await persistLocalExchange(line, text, { includeUser: false });
|
|
3879
|
+
return { type: 'system', text };
|
|
3880
|
+
}
|
|
3881
|
+
const written = await writeReflectSkillDraft({
|
|
3882
|
+
draft: candidate,
|
|
3883
|
+
scope: state.targetScope || 'project',
|
|
3884
|
+
workspaceRoot: process.cwd()
|
|
3885
|
+
});
|
|
3886
|
+
currentSession.planState = null;
|
|
3887
|
+
executionMode = 'auto';
|
|
3888
|
+
await reloadCommandsAndSkills();
|
|
3889
|
+
const text = `Reflect skill written and loaded: /${written.draft.name}\nPath: ${written.filePath}`;
|
|
3890
|
+
await persistLocalExchange(line, text, { includeUser: false });
|
|
3891
|
+
return { type: 'system', text };
|
|
3892
|
+
}
|
|
3524
3893
|
if (!hasPendingPlanApproval(currentSession)) {
|
|
3525
3894
|
return { type: 'system', text: 'No pending plan approval. Use /plan auto <goal> first.' };
|
|
3526
3895
|
}
|
|
@@ -3544,6 +3913,35 @@ export async function createChatRuntime({
|
|
|
3544
3913
|
return { type: 'assistant', text: result.text, aborted: !!result.aborted };
|
|
3545
3914
|
}
|
|
3546
3915
|
if (parsedInput.command === 'edit') {
|
|
3916
|
+
if (hasPendingReflectSkill(currentSession)) {
|
|
3917
|
+
const feedback = parsedInput.args.join(' ').trim();
|
|
3918
|
+
if (!feedback) {
|
|
3919
|
+
return { type: 'system', text: 'Usage: /edit <feedback>' };
|
|
3920
|
+
}
|
|
3921
|
+
const state = { ...currentSession.planState };
|
|
3922
|
+
const previousDraft = Array.isArray(state.candidates) ? state.candidates[0] : null;
|
|
3923
|
+
const drafts = await buildReflectSkillDraft({
|
|
3924
|
+
request: state.request || '',
|
|
3925
|
+
scope: state.targetScope || 'project',
|
|
3926
|
+
session: currentSession,
|
|
3927
|
+
config,
|
|
3928
|
+
model,
|
|
3929
|
+
systemPrompt: activeReplySystemPrompt,
|
|
3930
|
+
previousDraft,
|
|
3931
|
+
feedback
|
|
3932
|
+
});
|
|
3933
|
+
currentSession.planState = {
|
|
3934
|
+
...state,
|
|
3935
|
+
candidates: attachReflectTargets({
|
|
3936
|
+
candidates: drafts,
|
|
3937
|
+
scope: state.targetScope || 'project',
|
|
3938
|
+
workspaceRoot: process.cwd()
|
|
3939
|
+
})
|
|
3940
|
+
};
|
|
3941
|
+
const text = `Reflect skill draft revised.\n${buildPendingReflectSkillMessage(currentSession.planState)}`;
|
|
3942
|
+
await persistLocalExchange(line, text);
|
|
3943
|
+
return { type: 'system', text };
|
|
3944
|
+
}
|
|
3547
3945
|
if (!hasPendingPlanApproval(currentSession)) {
|
|
3548
3946
|
return { type: 'system', text: 'No pending plan approval. Use /plan auto <goal> first.' };
|
|
3549
3947
|
}
|
|
@@ -3564,6 +3962,23 @@ export async function createChatRuntime({
|
|
|
3564
3962
|
await persistLocalExchange(line, text);
|
|
3565
3963
|
return { type: 'system', text };
|
|
3566
3964
|
}
|
|
3965
|
+
if (parsedInput.command === 'no') {
|
|
3966
|
+
if (hasPendingReflectSkill(currentSession)) {
|
|
3967
|
+
currentSession.planState = null;
|
|
3968
|
+
executionMode = 'auto';
|
|
3969
|
+
const text = 'Reflect skill draft discarded.';
|
|
3970
|
+
await persistLocalExchange(line, text, { includeUser: false });
|
|
3971
|
+
return { type: 'system', text };
|
|
3972
|
+
}
|
|
3973
|
+
if (hasPendingPlanApproval(currentSession)) {
|
|
3974
|
+
currentSession.planState = null;
|
|
3975
|
+
executionMode = 'auto';
|
|
3976
|
+
const text = 'Pending plan rejected and cleared.';
|
|
3977
|
+
await persistLocalExchange(line, text, { includeUser: false });
|
|
3978
|
+
return { type: 'system', text };
|
|
3979
|
+
}
|
|
3980
|
+
return { type: 'system', text: 'No pending reflect skill draft.' };
|
|
3981
|
+
}
|
|
3567
3982
|
if (parsedInput.command === 'reject') {
|
|
3568
3983
|
if (!hasPendingPlanApproval(currentSession)) {
|
|
3569
3984
|
return { type: 'system', text: 'No pending plan approval.' };
|
|
@@ -3776,7 +4191,7 @@ export async function createChatRuntime({
|
|
|
3776
4191
|
if (sub === 'list') {
|
|
3777
4192
|
return {
|
|
3778
4193
|
type: 'system',
|
|
3779
|
-
text: 'Sub-agent roles: planner, coder, reviewer, tester, summarizer\nUse: /agents run <role> <task>'
|
|
4194
|
+
text: 'Sub-agent roles: planner, advisor, coder, reviewer, tester, summarizer\nUse: /agents run <role> <task>'
|
|
3780
4195
|
};
|
|
3781
4196
|
}
|
|
3782
4197
|
if (sub === 'run') {
|
|
@@ -3784,7 +4199,7 @@ export async function createChatRuntime({
|
|
|
3784
4199
|
const task = parsedInput.args.slice(2).join(' ').trim();
|
|
3785
4200
|
if (!role || !task) return { type: 'system', text: 'Usage: /agents run <role> <task>' };
|
|
3786
4201
|
if (!SUB_AGENT_ROLES.includes(role)) {
|
|
3787
|
-
return { type: 'system', text: 'Unknown role. Allowed: planner|coder|reviewer|tester|summarizer' };
|
|
4202
|
+
return { type: 'system', text: 'Unknown role. Allowed: planner|advisor|coder|reviewer|tester|summarizer' };
|
|
3788
4203
|
}
|
|
3789
4204
|
const output = await runSubAgentTask({
|
|
3790
4205
|
role,
|
|
@@ -3822,13 +4237,9 @@ export async function createChatRuntime({
|
|
|
3822
4237
|
messageCount: Number(s.messageCount || 0)
|
|
3823
4238
|
}));
|
|
3824
4239
|
if (sessions.length === 0) return { type: 'system', text: 'No sessions found' };
|
|
3825
|
-
const rows = sessions.map(
|
|
3826
|
-
(s, idx) =>
|
|
3827
|
-
`${idx + 1}. ${s.id} | msgs:${s.messageCount} | updated:${s.updatedAt || '-'}${s.preview ? ` | ${s.preview}` : ''}`
|
|
3828
|
-
);
|
|
3829
4240
|
return {
|
|
3830
4241
|
type: 'system',
|
|
3831
|
-
text:
|
|
4242
|
+
text: formatHistoryList({ currentSession, sessions })
|
|
3832
4243
|
};
|
|
3833
4244
|
}
|
|
3834
4245
|
if (sub === 'current') {
|
|
@@ -3971,6 +4382,37 @@ export async function createChatRuntime({
|
|
|
3971
4382
|
return { type: 'system', text: `Dream failed: ${err.message}` };
|
|
3972
4383
|
}
|
|
3973
4384
|
}
|
|
4385
|
+
if (parsedInput.command === 'reflect') {
|
|
4386
|
+
const parsedReflect = parseReflectScope(parsedInput.args);
|
|
4387
|
+
const drafts = await buildReflectSkillDraft({
|
|
4388
|
+
request: parsedReflect.request,
|
|
4389
|
+
scope: parsedReflect.scope,
|
|
4390
|
+
session: currentSession,
|
|
4391
|
+
config,
|
|
4392
|
+
model,
|
|
4393
|
+
systemPrompt: activeReplySystemPrompt
|
|
4394
|
+
});
|
|
4395
|
+
const candidates = attachReflectTargets({
|
|
4396
|
+
candidates: drafts,
|
|
4397
|
+
scope: parsedReflect.scope,
|
|
4398
|
+
workspaceRoot: process.cwd()
|
|
4399
|
+
});
|
|
4400
|
+
if (candidates.length === 0) {
|
|
4401
|
+
const text = 'Reflect found no reusable skill candidate.';
|
|
4402
|
+
await persistLocalExchange(line, text);
|
|
4403
|
+
return { type: 'system', text };
|
|
4404
|
+
}
|
|
4405
|
+
currentSession.planState = {
|
|
4406
|
+
status: 'pending_reflect_skill',
|
|
4407
|
+
source: 'reflect',
|
|
4408
|
+
targetScope: parsedReflect.scope,
|
|
4409
|
+
request: parsedReflect.request,
|
|
4410
|
+
candidates
|
|
4411
|
+
};
|
|
4412
|
+
const text = buildPendingReflectSkillMessage(currentSession.planState);
|
|
4413
|
+
await persistLocalExchange(line, text);
|
|
4414
|
+
return { type: 'system', text };
|
|
4415
|
+
}
|
|
3974
4416
|
if (parsedInput.command === 'retry') {
|
|
3975
4417
|
const lastUser = [...currentSession.messages].reverse().find((m) => m.role === 'user');
|
|
3976
4418
|
if (!lastUser?.content) {
|
|
@@ -4096,7 +4538,7 @@ export async function createChatRuntime({
|
|
|
4096
4538
|
if (!custom) {
|
|
4097
4539
|
return { type: 'system', text: `Unknown slash command: /${parsedInput.command}` };
|
|
4098
4540
|
}
|
|
4099
|
-
if (custom.metadata.type === 'skill' && config
|
|
4541
|
+
if (custom.metadata.type === 'skill' && !isSkillEnabled(config, custom.name, custom)) {
|
|
4100
4542
|
return { type: 'system', text: `Skill is disabled: ${custom.name}` };
|
|
4101
4543
|
}
|
|
4102
4544
|
|
|
@@ -4218,7 +4660,6 @@ export async function createChatRuntime({
|
|
|
4218
4660
|
}
|
|
4219
4661
|
|
|
4220
4662
|
const expandedText = await expandFileMentions(parsedInput.text, process.cwd());
|
|
4221
|
-
await saveDirectMemoryPrompt(expandedText);
|
|
4222
4663
|
const autoRoute = classifyAutoRoute(expandedText);
|
|
4223
4664
|
if (autoRoute.autoPlan) {
|
|
4224
4665
|
await maybeAutoDreamFromRuntime();
|
|
@@ -4247,7 +4688,7 @@ export async function createChatRuntime({
|
|
|
4247
4688
|
return { type: 'system', text };
|
|
4248
4689
|
}
|
|
4249
4690
|
|
|
4250
|
-
const selectedAutoSkills = autoRoute.selectedSkills.filter((name) => isSkillEnabled(config, name));
|
|
4691
|
+
const selectedAutoSkills = autoRoute.selectedSkills.filter((name) => isSkillEnabled(config, name, commands.get(name)));
|
|
4251
4692
|
if (selectedAutoSkills.length > 0 && onAgentEvent) {
|
|
4252
4693
|
onAgentEvent({
|
|
4253
4694
|
type: 'skill:auto',
|
|
@@ -4270,6 +4711,7 @@ export async function createChatRuntime({
|
|
|
4270
4711
|
executionMode,
|
|
4271
4712
|
signal
|
|
4272
4713
|
});
|
|
4714
|
+
await saveDirectMemoryPrompt(expandedText);
|
|
4273
4715
|
await captureUserPromptForDream(expandedText);
|
|
4274
4716
|
return { type: 'assistant', text: result.text, aborted: !!result.aborted };
|
|
4275
4717
|
};
|