codemini-cli 0.1.11 → 0.1.13
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 +1 -1
- package/src/core/agent-loop.js +17 -0
- package/src/core/chat-runtime.js +477 -46
- package/src/core/config-store.js +39 -3
- package/src/core/reply-language.js +25 -0
- package/src/core/shell-profile.js +1 -1
- package/src/core/soul.js +3 -1
- package/src/core/tools.js +884 -8
- package/src/tui/chat-app.js +255 -16
package/src/core/chat-runtime.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
estimateMessagesTokens,
|
|
27
27
|
parseCompactArgs
|
|
28
28
|
} from './context-compact.js';
|
|
29
|
+
import { buildSystemPromptWithReplyLanguage } from './reply-language.js';
|
|
29
30
|
import { buildSystemPromptWithSoul } from './soul.js';
|
|
30
31
|
|
|
31
32
|
function toOpenAIMessages(sessionMessages) {
|
|
@@ -60,6 +61,46 @@ function nowStamp() {
|
|
|
60
61
|
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
function prioritizeByPreferredOrder(items, preferredOrder) {
|
|
65
|
+
const source = Array.isArray(items) ? items : [];
|
|
66
|
+
const priorities = new Map((Array.isArray(preferredOrder) ? preferredOrder : []).map((value, index) => [value, index]));
|
|
67
|
+
return [...source].sort((left, right) => {
|
|
68
|
+
const leftRank = priorities.has(left) ? priorities.get(left) : Number.MAX_SAFE_INTEGER;
|
|
69
|
+
const rightRank = priorities.has(right) ? priorities.get(right) : Number.MAX_SAFE_INTEGER;
|
|
70
|
+
if (leftRank !== rightRank) return leftRank - rightRank;
|
|
71
|
+
return source.indexOf(left) - source.indexOf(right);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function describeConfigKey(key, mode = 'set') {
|
|
76
|
+
const labelMap = {
|
|
77
|
+
'gateway.base_url': 'gateway base URL',
|
|
78
|
+
'gateway.api_key': 'gateway API key',
|
|
79
|
+
'gateway.timeout_ms': 'gateway timeout in milliseconds',
|
|
80
|
+
'gateway.max_retries': 'gateway retry count',
|
|
81
|
+
'model.name': 'active model name',
|
|
82
|
+
'model.max_context_tokens': 'model context token limit',
|
|
83
|
+
'ui.reply_language': 'reply language',
|
|
84
|
+
'execution.mode': 'execution mode',
|
|
85
|
+
'execution.always_allow_tools': 'always-allowed tools',
|
|
86
|
+
'execution.max_steps': 'maximum tool steps',
|
|
87
|
+
'context.preflight_trigger_pct': 'preflight compact threshold',
|
|
88
|
+
'context.hard_limit_pct': 'hard compact threshold',
|
|
89
|
+
'context.tool_result_max_chars': 'tool result character limit',
|
|
90
|
+
'context.read_file_default_lines': 'default read_file line window',
|
|
91
|
+
'context.read_file_max_chars': 'read_file character limit',
|
|
92
|
+
'sessions.max_sessions': 'stored session limit',
|
|
93
|
+
'sessions.retention_days': 'session retention days',
|
|
94
|
+
'shell.default': 'default shell',
|
|
95
|
+
'shell.timeout_ms': 'shell timeout in milliseconds',
|
|
96
|
+
'context.max_tokens': 'context token budget',
|
|
97
|
+
'policy.safe_mode': 'safe mode switch',
|
|
98
|
+
'policy.allow_dangerous_commands': 'dangerous command allowance'
|
|
99
|
+
};
|
|
100
|
+
const label = labelMap[key] || key;
|
|
101
|
+
return mode === 'get' ? `show the ${label}` : `set the ${label}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
63
104
|
const SUB_AGENT_ROLES = ['planner', 'coder', 'reviewer', 'tester'];
|
|
64
105
|
const SUB_AGENT_CONTEXT_MAX_MESSAGES = 4;
|
|
65
106
|
const SUB_AGENT_CONTEXT_MAX_CHARS = 1200;
|
|
@@ -76,6 +117,8 @@ function getSubAgentRolePrompt(role) {
|
|
|
76
117
|
'You are a review sub-agent. Focus on bugs, regressions, edge cases, and missing tests.',
|
|
77
118
|
'Start with the focused files or directories handed to you. Do not roam unrelated parts of the repo unless the handed-off evidence is insufficient.',
|
|
78
119
|
'Use this exact output structure:',
|
|
120
|
+
'Acceptance Status:',
|
|
121
|
+
'- <met|unmet|unverified> :: <acceptance checklist item or "none">',
|
|
79
122
|
'Findings:',
|
|
80
123
|
'- <bug, regression, risk, or "none">',
|
|
81
124
|
'Verified:',
|
|
@@ -92,6 +135,8 @@ function getSubAgentRolePrompt(role) {
|
|
|
92
135
|
'Prefer running concrete verification commands over only suggesting them.',
|
|
93
136
|
'Start with the focused files or directories handed to you. Verify those artifacts first before scanning the wider repo.',
|
|
94
137
|
'Use this exact output structure:',
|
|
138
|
+
'Acceptance Status:',
|
|
139
|
+
'- <met|unmet|unverified> :: <acceptance checklist item or "none">',
|
|
95
140
|
'Verified:',
|
|
96
141
|
'- <commands run and evidence>',
|
|
97
142
|
'Not Verified:',
|
|
@@ -277,6 +322,114 @@ function buildFocusedTaskNote(role, focusPaths) {
|
|
|
277
322
|
return '';
|
|
278
323
|
}
|
|
279
324
|
|
|
325
|
+
function normalizeGoalClauseText(value) {
|
|
326
|
+
return String(value || '')
|
|
327
|
+
.replace(/^[\s\-*0-9.)、,,:;]+/g, '')
|
|
328
|
+
.replace(/\s+/g, ' ')
|
|
329
|
+
.trim();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function sentenceCaseRequirement(value) {
|
|
333
|
+
const text = normalizeGoalClauseText(value);
|
|
334
|
+
if (!text) return '';
|
|
335
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function deriveGoalRequirements(goal) {
|
|
339
|
+
const rawGoal = String(goal || '').trim();
|
|
340
|
+
if (!rawGoal) return [];
|
|
341
|
+
|
|
342
|
+
const normalized = rawGoal
|
|
343
|
+
.replace(/\r\n?/g, '\n')
|
|
344
|
+
.replace(/[;。]/g, ',')
|
|
345
|
+
.replace(/\band then\b/gi, ',')
|
|
346
|
+
.replace(/\bthen\b/gi, ',')
|
|
347
|
+
.replace(/\bplus\b/gi, ',')
|
|
348
|
+
.replace(/\s+(?:and|并且|而且|以及)\s+/gi, ', ')
|
|
349
|
+
.replace(/\n+/g, ', ');
|
|
350
|
+
|
|
351
|
+
const roughParts = normalized
|
|
352
|
+
.split(/\s*,\s*/)
|
|
353
|
+
.map((part) => normalizeGoalClauseText(part))
|
|
354
|
+
.filter(Boolean);
|
|
355
|
+
|
|
356
|
+
const requirements = [];
|
|
357
|
+
const seen = new Set();
|
|
358
|
+
|
|
359
|
+
for (const part of roughParts) {
|
|
360
|
+
const lowered = part.toLowerCase();
|
|
361
|
+
if (/\btrim\b/.test(lowered) && !/\bwhitespace\b/.test(lowered)) {
|
|
362
|
+
const label = 'Trim whitespace in the returned greeting';
|
|
363
|
+
if (!seen.has(label)) {
|
|
364
|
+
seen.add(label);
|
|
365
|
+
requirements.push(label);
|
|
366
|
+
}
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (/\btrim\b/.test(lowered) && /\bwhitespace\b/.test(lowered)) {
|
|
370
|
+
const label = 'Trim whitespace in the returned greeting';
|
|
371
|
+
if (!seen.has(label)) {
|
|
372
|
+
seen.add(label);
|
|
373
|
+
requirements.push(label);
|
|
374
|
+
}
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (/(exclamation mark|感叹号|!)/i.test(part)) {
|
|
378
|
+
const label = 'Preserve the exclamation mark';
|
|
379
|
+
if (!seen.has(label)) {
|
|
380
|
+
seen.add(label);
|
|
381
|
+
requirements.push(label);
|
|
382
|
+
}
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const label = sentenceCaseRequirement(part);
|
|
386
|
+
if (!label || seen.has(label)) continue;
|
|
387
|
+
seen.add(label);
|
|
388
|
+
requirements.push(label);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (requirements.length === 0) {
|
|
392
|
+
return [sentenceCaseRequirement(rawGoal)].filter(Boolean);
|
|
393
|
+
}
|
|
394
|
+
return requirements.slice(0, 6);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function isLightweightAutoPlanGoal(goal, requirements = []) {
|
|
398
|
+
const text = String(goal || '').trim();
|
|
399
|
+
if (!text) return false;
|
|
400
|
+
if (requirements.length !== 1) return false;
|
|
401
|
+
if (text.length > 140) return false;
|
|
402
|
+
if (/\b(plan|spec|design|architecture|roadmap|strategy|migration|refactor)\b/i.test(text)) return false;
|
|
403
|
+
if (/\b(ensure|verify|review|test|validate|make sure|confirm)\b/i.test(text)) return false;
|
|
404
|
+
if (/[;。]/.test(text)) return false;
|
|
405
|
+
return /\b(add|update|fix|rename|trim|export|create|remove|change|implement)\b/i.test(text);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function buildGoalRequirementPacket(goal, role) {
|
|
409
|
+
const rawGoal = trimInlineText(goal, 800);
|
|
410
|
+
if (!rawGoal) return '';
|
|
411
|
+
const requirements = deriveGoalRequirements(goal);
|
|
412
|
+
const lines = ['Original goal:', rawGoal];
|
|
413
|
+
if (requirements.length > 0) {
|
|
414
|
+
lines.push('Acceptance checklist:');
|
|
415
|
+
for (const requirement of requirements) {
|
|
416
|
+
lines.push(`- ${requirement}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (role === 'reviewer') {
|
|
420
|
+
lines.push('Review against the original goal, not just local code quality.');
|
|
421
|
+
lines.push('Check each acceptance item explicitly before deciding there are no findings.');
|
|
422
|
+
lines.push('If any requested behavior is missing, incorrect, or only partially implemented, report it in Findings.');
|
|
423
|
+
} else if (role === 'tester') {
|
|
424
|
+
lines.push('Verify the implementation against the original goal, not just syntax or smoke checks.');
|
|
425
|
+
lines.push('Check each acceptance item explicitly before calling the work verified.');
|
|
426
|
+
lines.push('If any requested behavior is unverified or contradicted by evidence, list it under Not Verified or Failures.');
|
|
427
|
+
} else if (role === 'coder') {
|
|
428
|
+
lines.push('Implement against the acceptance checklist, not only the broad wording of the goal.');
|
|
429
|
+
}
|
|
430
|
+
return lines.join('\n');
|
|
431
|
+
}
|
|
432
|
+
|
|
280
433
|
async function pathExists(targetPath) {
|
|
281
434
|
try {
|
|
282
435
|
await fs.access(targetPath);
|
|
@@ -466,7 +619,16 @@ function normalizeAutoPlan(parsed, goal) {
|
|
|
466
619
|
|
|
467
620
|
function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
468
621
|
const source = Array.isArray(plan?.steps) ? plan.steps : [];
|
|
622
|
+
const requirements = deriveGoalRequirements(goal);
|
|
623
|
+
const lightweightGoal = isLightweightAutoPlanGoal(goal, requirements);
|
|
469
624
|
const implementationSteps = source.filter((step) => step.role !== 'reviewer' && step.role !== 'tester');
|
|
625
|
+
const primaryImplementationStep =
|
|
626
|
+
implementationSteps.find((step) => step.role === 'coder') ||
|
|
627
|
+
implementationSteps[0] || {
|
|
628
|
+
title: 'Implement requested change',
|
|
629
|
+
role: 'coder',
|
|
630
|
+
task: `Implement the requested change for: ${goal}`
|
|
631
|
+
};
|
|
470
632
|
const reviewerStep = source.find((step) => step.role === 'reviewer') || {
|
|
471
633
|
title: 'Review implementation',
|
|
472
634
|
role: 'reviewer',
|
|
@@ -478,6 +640,13 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
478
640
|
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.`
|
|
479
641
|
};
|
|
480
642
|
|
|
643
|
+
if (lightweightGoal) {
|
|
644
|
+
return {
|
|
645
|
+
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
646
|
+
steps: [primaryImplementationStep, testerStep]
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
481
650
|
return {
|
|
482
651
|
summary: String(plan?.summary || `Auto plan for: ${goal}`).trim(),
|
|
483
652
|
steps: [...implementationSteps.slice(0, 6), reviewerStep, testerStep]
|
|
@@ -487,18 +656,75 @@ function enforceAutoPlanGuardrailSteps(plan, goal) {
|
|
|
487
656
|
function looksLikeSuccessfulStepOutput(text = '') {
|
|
488
657
|
const value = String(text || '').trim();
|
|
489
658
|
if (!value) return false;
|
|
490
|
-
|
|
491
|
-
|
|
659
|
+
const failureBullet = extractSectionFirstBullet(value, 'Failures');
|
|
660
|
+
const errorBullet = extractSectionFirstBullet(value, 'Error');
|
|
661
|
+
const nextActionBullet = extractSectionFirstBullet(value, 'Next Action');
|
|
662
|
+
const acceptanceFailures = extractAcceptanceStatusItems(value).filter((item) => item.status !== 'met');
|
|
663
|
+
if (errorBullet && !/^none\b/i.test(errorBullet)) return false;
|
|
664
|
+
if (failureBullet && !/^none\b/i.test(failureBullet)) return false;
|
|
665
|
+
if (acceptanceFailures.length > 0) return false;
|
|
666
|
+
if (nextActionBullet && /^retry\b/i.test(nextActionBullet)) return false;
|
|
492
667
|
return true;
|
|
493
668
|
}
|
|
494
669
|
|
|
670
|
+
function stepOutputHasFailureSignals(role, text = '') {
|
|
671
|
+
const value = String(text || '').trim();
|
|
672
|
+
if (!value) return true;
|
|
673
|
+
const errorBullet = extractSectionFirstBullet(value, 'Error');
|
|
674
|
+
const failureBullet = extractSectionFirstBullet(value, 'Failures');
|
|
675
|
+
const findingsBullet = extractSectionFirstBullet(value, 'Findings');
|
|
676
|
+
const nextActionBullet = extractSectionFirstBullet(value, 'Next Action');
|
|
677
|
+
const acceptanceFailures = extractAcceptanceStatusItems(value).filter((item) => item.status !== 'met');
|
|
678
|
+
if (errorBullet && !/^none\b/i.test(errorBullet)) return true;
|
|
679
|
+
if (failureBullet && !/^none\b/i.test(failureBullet)) return true;
|
|
680
|
+
if (acceptanceFailures.length > 0) return true;
|
|
681
|
+
if (role === 'reviewer' && findingsBullet && !/^none\b/i.test(findingsBullet)) return true;
|
|
682
|
+
if (nextActionBullet && /^(fix|retry|correct|repair)\b/i.test(nextActionBullet)) return true;
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function extractSectionFirstBullet(text = '', heading = '') {
|
|
687
|
+
const escaped = String(heading || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
688
|
+
const match = String(text || '').match(new RegExp(String.raw`(^|\n)\s*${escaped}\s*:\s*(?:\n|\r\n?)+\s*-\s*([^\n\r]+)`, 'i'));
|
|
689
|
+
return String(match?.[2] || '').trim();
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function extractSectionBullets(text = '', heading = '') {
|
|
693
|
+
const escaped = String(heading || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
694
|
+
const value = String(text || '');
|
|
695
|
+
const headingMatch = value.match(new RegExp(String.raw`(^|\n)\s*${escaped}\s*:\s*(?:\n|\r\n?)`, 'i'));
|
|
696
|
+
if (!headingMatch || headingMatch.index == null) return [];
|
|
697
|
+
const start = headingMatch.index + headingMatch[0].length;
|
|
698
|
+
const after = value.slice(start);
|
|
699
|
+
const nextHeading = after.search(/\n\s*[A-Za-z][A-Za-z ]+\s*:\s*(?:\n|\r\n?)/);
|
|
700
|
+
const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
|
|
701
|
+
return section
|
|
702
|
+
.split(/\r?\n/)
|
|
703
|
+
.map((line) => line.match(/^\s*-\s*(.+)$/)?.[1]?.trim() || '')
|
|
704
|
+
.filter(Boolean);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function extractAcceptanceStatusItems(text = '') {
|
|
708
|
+
return extractSectionBullets(text, 'Acceptance Status')
|
|
709
|
+
.map((item) => {
|
|
710
|
+
const match = String(item).match(/^(met|unmet|unverified)\s*::\s*(.+)$/i);
|
|
711
|
+
if (!match) return null;
|
|
712
|
+
return {
|
|
713
|
+
status: String(match[1] || '').toLowerCase(),
|
|
714
|
+
label: String(match[2] || '').trim()
|
|
715
|
+
};
|
|
716
|
+
})
|
|
717
|
+
.filter(Boolean);
|
|
718
|
+
}
|
|
719
|
+
|
|
495
720
|
function buildAutoPlanSystemSummary(auto) {
|
|
496
721
|
const statusTitle =
|
|
497
722
|
auto.failedCount > 0 ? 'Auto plan finished with failures' : auto.warningCount > 0 ? 'Auto plan finished with warnings' : 'Auto plan finished';
|
|
498
723
|
const lines = [
|
|
499
724
|
statusTitle,
|
|
500
725
|
`File: ${auto.filePath}`,
|
|
501
|
-
`Summary: ${auto.summary || '-'}`,
|
|
726
|
+
`Plan Summary: ${auto.summary || '-'}`,
|
|
727
|
+
`Final Summary: ${auto.finalSummary || auto.summary || '-'}`,
|
|
502
728
|
`Steps: ${auto.steps.length} total`,
|
|
503
729
|
`Completed: ${auto.completedCount}`,
|
|
504
730
|
`Warnings: ${auto.warningCount}`,
|
|
@@ -513,6 +739,99 @@ function buildAutoPlanSystemSummary(auto) {
|
|
|
513
739
|
return lines.join('\n');
|
|
514
740
|
}
|
|
515
741
|
|
|
742
|
+
function buildAutoPlanFinalSummaryUserPrompt({ goal, autoPlan, runItems, planningError }) {
|
|
743
|
+
const lines = [];
|
|
744
|
+
lines.push('Create a final execution summary for an auto-generated implementation/test plan.');
|
|
745
|
+
lines.push('Keep it concise, high-signal, and outcome-focused.');
|
|
746
|
+
lines.push('Include: overall result, what was verified, what is still pending, and the best next action.');
|
|
747
|
+
lines.push('Use plain text only. Do not use markdown fences.');
|
|
748
|
+
lines.push('');
|
|
749
|
+
lines.push(`Goal: ${goal}`);
|
|
750
|
+
lines.push(`Plan Summary: ${autoPlan?.summary || `Auto plan for: ${goal}`}`);
|
|
751
|
+
if (planningError) {
|
|
752
|
+
lines.push(`Planning Error: ${planningError}`);
|
|
753
|
+
}
|
|
754
|
+
lines.push('');
|
|
755
|
+
lines.push('Executed Steps:');
|
|
756
|
+
runItems.forEach((item, idx) => {
|
|
757
|
+
lines.push(`${idx + 1}. [${item.role}] ${item.title}`);
|
|
758
|
+
if (item.failed) {
|
|
759
|
+
lines.push(`Status: failed`);
|
|
760
|
+
} else if (item.warning) {
|
|
761
|
+
lines.push(`Status: warning`);
|
|
762
|
+
} else {
|
|
763
|
+
lines.push(`Status: completed`);
|
|
764
|
+
}
|
|
765
|
+
if (item.error) {
|
|
766
|
+
lines.push(`Error: ${item.error}`);
|
|
767
|
+
}
|
|
768
|
+
if (item.warning) {
|
|
769
|
+
lines.push(`Warning: ${item.warning}`);
|
|
770
|
+
}
|
|
771
|
+
lines.push(`Output: ${trimInlineText(item.output || '(empty)', 500)}`);
|
|
772
|
+
if (Array.isArray(item.artifactPaths) && item.artifactPaths.length > 0) {
|
|
773
|
+
lines.push(`Artifacts: ${item.artifactPaths.slice(0, 5).join(', ')}`);
|
|
774
|
+
}
|
|
775
|
+
lines.push('');
|
|
776
|
+
});
|
|
777
|
+
return lines.join('\n').trim();
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
async function buildAutoPlanFinalSummary({
|
|
781
|
+
goal,
|
|
782
|
+
autoPlan,
|
|
783
|
+
runItems,
|
|
784
|
+
planningError,
|
|
785
|
+
config,
|
|
786
|
+
model,
|
|
787
|
+
systemPrompt
|
|
788
|
+
}) {
|
|
789
|
+
const fallbackParts = [];
|
|
790
|
+
if (runItems.some((item) => item.failed || item.error)) {
|
|
791
|
+
fallbackParts.push('Execution finished with failed steps.');
|
|
792
|
+
} else if (runItems.some((item) => item.warning)) {
|
|
793
|
+
fallbackParts.push('Execution finished with warnings.');
|
|
794
|
+
} else {
|
|
795
|
+
fallbackParts.push('Execution finished successfully.');
|
|
796
|
+
}
|
|
797
|
+
const verifiedTitles = runItems.filter((item) => !item.failed).map((item) => item.title);
|
|
798
|
+
const pendingTitles = runItems.filter((item) => item.failed || item.warning).map((item) => item.title);
|
|
799
|
+
if (verifiedTitles.length > 0) {
|
|
800
|
+
fallbackParts.push(`Completed: ${verifiedTitles.slice(0, 4).join(', ')}.`);
|
|
801
|
+
}
|
|
802
|
+
if (pendingTitles.length > 0) {
|
|
803
|
+
fallbackParts.push(`Needs follow-up: ${pendingTitles.slice(0, 4).join(', ')}.`);
|
|
804
|
+
}
|
|
805
|
+
const fallbackSummary = fallbackParts.join(' ');
|
|
806
|
+
|
|
807
|
+
if (runItems.some((item) => item.failed || item.error)) {
|
|
808
|
+
return fallbackSummary;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
try {
|
|
812
|
+
const result = await createChatCompletion({
|
|
813
|
+
baseUrl: config.gateway.base_url,
|
|
814
|
+
apiKey: config.gateway.api_key,
|
|
815
|
+
model: model || config.model.name,
|
|
816
|
+
messages: [
|
|
817
|
+
{
|
|
818
|
+
role: 'system',
|
|
819
|
+
content: `${systemPrompt}\nYou are writing the final execution summary for a completed auto plan. Focus on closure, verification status, and the next action.`
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
role: 'user',
|
|
823
|
+
content: buildAutoPlanFinalSummaryUserPrompt({ goal, autoPlan, runItems, planningError })
|
|
824
|
+
}
|
|
825
|
+
],
|
|
826
|
+
timeoutMs: config.gateway.timeout_ms || 90000,
|
|
827
|
+
maxRetries: config.gateway.max_retries ?? 2
|
|
828
|
+
});
|
|
829
|
+
return trimInlineText(result.text || '', 600) || fallbackSummary;
|
|
830
|
+
} catch {
|
|
831
|
+
return fallbackSummary;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
516
835
|
async function writeMarkdownInCoderDir(subDir, title, body, fallbackName, sessionId) {
|
|
517
836
|
const parts = [process.cwd(), '.coder', subDir];
|
|
518
837
|
if (sessionId) parts.push(String(sessionId));
|
|
@@ -943,6 +1262,7 @@ async function askModel({
|
|
|
943
1262
|
async function runSubAgentTask({
|
|
944
1263
|
role,
|
|
945
1264
|
task,
|
|
1265
|
+
goal = '',
|
|
946
1266
|
priorSteps = [],
|
|
947
1267
|
parentSession,
|
|
948
1268
|
config,
|
|
@@ -957,8 +1277,18 @@ async function runSubAgentTask({
|
|
|
957
1277
|
const handoffPacket = buildStepArtifactPacket(priorSteps, role);
|
|
958
1278
|
const handoffFocusPaths = collectStepArtifacts(priorSteps, role)?.focusPaths || [];
|
|
959
1279
|
const focusedTaskNote = buildFocusedTaskNote(role, handoffFocusPaths);
|
|
1280
|
+
const goalRequirementPacket = buildGoalRequirementPacket(goal, role);
|
|
960
1281
|
const verificationPacket = role === 'tester' ? await buildTesterVerificationPacket(handoffFocusPaths) : '';
|
|
961
|
-
const scopedTask = [
|
|
1282
|
+
const scopedTask = [
|
|
1283
|
+
contextPacket,
|
|
1284
|
+
goalRequirementPacket,
|
|
1285
|
+
evidencePacket,
|
|
1286
|
+
handoffPacket,
|
|
1287
|
+
verificationPacket,
|
|
1288
|
+
focusedTaskNote,
|
|
1289
|
+
'Task:',
|
|
1290
|
+
task
|
|
1291
|
+
]
|
|
962
1292
|
.filter(Boolean)
|
|
963
1293
|
.join('\n\n');
|
|
964
1294
|
let blockedCount = 0;
|
|
@@ -1015,6 +1345,7 @@ async function buildAutoPlanAndRun({
|
|
|
1015
1345
|
onAgentEvent,
|
|
1016
1346
|
sessionId
|
|
1017
1347
|
}) {
|
|
1348
|
+
const requirementPacket = buildGoalRequirementPacket(goal, 'planner');
|
|
1018
1349
|
const plannerPrompt =
|
|
1019
1350
|
'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|coder|reviewer|tester","task":"..."}]}. No markdown. Always include final reviewer and tester steps.';
|
|
1020
1351
|
let autoPlan = {
|
|
@@ -1037,7 +1368,13 @@ async function buildAutoPlanAndRun({
|
|
|
1037
1368
|
{ role: 'system', content: `${systemPrompt}\n${plannerPrompt}` },
|
|
1038
1369
|
{
|
|
1039
1370
|
role: 'user',
|
|
1040
|
-
content:
|
|
1371
|
+
content: [
|
|
1372
|
+
'Create an execution plan and assign best sub-agent role for each step.',
|
|
1373
|
+
requirementPacket,
|
|
1374
|
+
'The final steps must include review and testing/verification unless the goal is a tiny single-change task, in which case you may keep only one implementation step plus one testing/verification step.'
|
|
1375
|
+
]
|
|
1376
|
+
.filter(Boolean)
|
|
1377
|
+
.join('\n')
|
|
1041
1378
|
}
|
|
1042
1379
|
],
|
|
1043
1380
|
timeoutMs: config.gateway.timeout_ms || 90000,
|
|
@@ -1050,18 +1387,20 @@ async function buildAutoPlanAndRun({
|
|
|
1050
1387
|
}
|
|
1051
1388
|
|
|
1052
1389
|
const runItems = [];
|
|
1390
|
+
const totalPlanSteps = autoPlan.steps.length + 1;
|
|
1053
1391
|
for (let i = 0; i < autoPlan.steps.length; i += 1) {
|
|
1054
1392
|
const step = autoPlan.steps[i];
|
|
1055
1393
|
if (onAgentEvent) {
|
|
1056
1394
|
onAgentEvent({
|
|
1057
1395
|
type: 'assistant:delta',
|
|
1058
|
-
text: `\n[plan] Step ${i + 1}/${
|
|
1396
|
+
text: `\n[plan] Step ${i + 1}/${totalPlanSteps} -> ${step.role}: ${step.title}\n`
|
|
1059
1397
|
});
|
|
1060
1398
|
}
|
|
1061
1399
|
try {
|
|
1062
1400
|
const stepResult = await runSubAgentTask({
|
|
1063
1401
|
role: step.role,
|
|
1064
1402
|
task: step.task,
|
|
1403
|
+
goal,
|
|
1065
1404
|
priorSteps: runItems,
|
|
1066
1405
|
parentSession: session,
|
|
1067
1406
|
config,
|
|
@@ -1070,14 +1409,20 @@ async function buildAutoPlanAndRun({
|
|
|
1070
1409
|
onAgentEvent
|
|
1071
1410
|
});
|
|
1072
1411
|
const outputLooksSuccessful = looksLikeSuccessfulStepOutput(stepResult.text);
|
|
1412
|
+
const outputHasFailureSignals = stepOutputHasFailureSignals(step.role, stepResult.text);
|
|
1073
1413
|
const warningParts = [];
|
|
1074
1414
|
if (stepResult.blockedCount > 0) warningParts.push(`${stepResult.blockedCount} blocked tool call(s)`);
|
|
1075
1415
|
if (stepResult.toolErrorCount > 0) warningParts.push(`${stepResult.toolErrorCount} tool error(s)`);
|
|
1076
1416
|
const warning = warningParts.length > 0 ? `sub-agent recovered after ${warningParts.join(', ')}` : '';
|
|
1077
|
-
const failed =
|
|
1417
|
+
const failed =
|
|
1418
|
+
stepResult.hasErrorLine ||
|
|
1419
|
+
outputHasFailureSignals ||
|
|
1420
|
+
(!outputLooksSuccessful && (stepResult.blockedCount > 0 || stepResult.toolErrorCount > 0));
|
|
1078
1421
|
let error = '';
|
|
1079
1422
|
if (stepResult.hasErrorLine) {
|
|
1080
1423
|
error = 'sub-agent output contains error line(s)';
|
|
1424
|
+
} else if (outputHasFailureSignals) {
|
|
1425
|
+
error = 'sub-agent output reports unmet requirements or failed verification';
|
|
1081
1426
|
} else if (failed && stepResult.blockedCount > 0) {
|
|
1082
1427
|
error = `sub-agent ended with ${stepResult.blockedCount} blocked tool call(s)`;
|
|
1083
1428
|
} else if (failed && stepResult.toolErrorCount > 0) {
|
|
@@ -1106,11 +1451,30 @@ async function buildAutoPlanAndRun({
|
|
|
1106
1451
|
const warningItems = runItems.filter((s) => !s.failed && s.warning);
|
|
1107
1452
|
const completedItems = runItems.filter((s) => !s.failed);
|
|
1108
1453
|
|
|
1454
|
+
if (onAgentEvent) {
|
|
1455
|
+
onAgentEvent({
|
|
1456
|
+
type: 'assistant:delta',
|
|
1457
|
+
text: `\n[plan] Step ${totalPlanSteps}/${totalPlanSteps} -> summarizer: Final summary\n`
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
const finalSummary = await buildAutoPlanFinalSummary({
|
|
1461
|
+
goal,
|
|
1462
|
+
autoPlan,
|
|
1463
|
+
runItems,
|
|
1464
|
+
planningError,
|
|
1465
|
+
config,
|
|
1466
|
+
model,
|
|
1467
|
+
systemPrompt
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1109
1470
|
const lines = [];
|
|
1110
1471
|
lines.push(`# Auto Plan: ${goal}`);
|
|
1111
1472
|
lines.push('');
|
|
1112
1473
|
lines.push(`## Summary`);
|
|
1113
1474
|
lines.push(autoPlan.summary || `Auto plan for: ${goal}`);
|
|
1475
|
+
lines.push('');
|
|
1476
|
+
lines.push('## Final Summary');
|
|
1477
|
+
lines.push(finalSummary || '(empty)');
|
|
1114
1478
|
if (planningError) {
|
|
1115
1479
|
lines.push('');
|
|
1116
1480
|
lines.push(`Planning Error: ${planningError}`);
|
|
@@ -1152,6 +1516,7 @@ async function buildAutoPlanAndRun({
|
|
|
1152
1516
|
return {
|
|
1153
1517
|
filePath,
|
|
1154
1518
|
summary: autoPlan.summary,
|
|
1519
|
+
finalSummary,
|
|
1155
1520
|
steps: autoPlan.steps,
|
|
1156
1521
|
completedCount: completedItems.length,
|
|
1157
1522
|
warningCount: warningItems.length,
|
|
@@ -1213,11 +1578,13 @@ export async function createChatRuntime({
|
|
|
1213
1578
|
const configKeyHints = [
|
|
1214
1579
|
'gateway.base_url',
|
|
1215
1580
|
'gateway.api_key',
|
|
1581
|
+
'model.name',
|
|
1582
|
+
'ui.reply_language',
|
|
1583
|
+
'execution.mode',
|
|
1584
|
+
'shell.default',
|
|
1216
1585
|
'gateway.timeout_ms',
|
|
1217
1586
|
'gateway.max_retries',
|
|
1218
|
-
'model.name',
|
|
1219
1587
|
'model.max_context_tokens',
|
|
1220
|
-
'execution.mode',
|
|
1221
1588
|
'execution.always_allow_tools',
|
|
1222
1589
|
'execution.max_steps',
|
|
1223
1590
|
'context.preflight_trigger_pct',
|
|
@@ -1227,13 +1594,34 @@ export async function createChatRuntime({
|
|
|
1227
1594
|
'context.read_file_max_chars',
|
|
1228
1595
|
'sessions.max_sessions',
|
|
1229
1596
|
'sessions.retention_days',
|
|
1230
|
-
'shell.default',
|
|
1231
1597
|
'shell.timeout_ms',
|
|
1232
1598
|
'context.max_tokens',
|
|
1233
1599
|
'policy.safe_mode',
|
|
1234
1600
|
'policy.allow_dangerous_commands'
|
|
1235
1601
|
];
|
|
1236
1602
|
|
|
1603
|
+
const commandPriorityOrder = [
|
|
1604
|
+
'/help',
|
|
1605
|
+
'/status',
|
|
1606
|
+
'/config',
|
|
1607
|
+
'/mode',
|
|
1608
|
+
'/plan',
|
|
1609
|
+
'/tasks',
|
|
1610
|
+
'/history',
|
|
1611
|
+
'/checkpoint',
|
|
1612
|
+
'/agents',
|
|
1613
|
+
'/compact',
|
|
1614
|
+
'/debug',
|
|
1615
|
+
'/retry'
|
|
1616
|
+
];
|
|
1617
|
+
const configSubcommandPriority = ['/config set', '/config get', '/config list', '/config reset'];
|
|
1618
|
+
const configSubcommandDescriptions = {
|
|
1619
|
+
'/config set': 'update a config value',
|
|
1620
|
+
'/config get': 'show a config value',
|
|
1621
|
+
'/config list': 'print the full config',
|
|
1622
|
+
'/config reset': 'reset config to defaults'
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1237
1625
|
const listCommandNames = () => {
|
|
1238
1626
|
const builtins = [
|
|
1239
1627
|
{ name: 'help', description: 'show chat help' },
|
|
@@ -1312,10 +1700,23 @@ export async function createChatRuntime({
|
|
|
1312
1700
|
'/status'
|
|
1313
1701
|
];
|
|
1314
1702
|
const compactKey = (value) => String(value || '').toLowerCase().replace(/[\/\s<>?]/g, '');
|
|
1703
|
+
const commandDescriptions = new Map();
|
|
1704
|
+
const registerSuggestion = (value, description = '') => {
|
|
1705
|
+
commandDescriptions.set(value, description);
|
|
1706
|
+
return { value, description };
|
|
1707
|
+
};
|
|
1708
|
+
const materializeSuggestions = (items) =>
|
|
1709
|
+
(Array.isArray(items) ? items : []).map((item) => {
|
|
1710
|
+
if (item && typeof item === 'object' && 'value' in item) return item;
|
|
1711
|
+
const value = String(item || '');
|
|
1712
|
+
return { value, description: commandDescriptions.get(value) || '' };
|
|
1713
|
+
});
|
|
1315
1714
|
const matchCompactTemplates = (value) => {
|
|
1316
1715
|
const needle = compactKey(value);
|
|
1317
1716
|
if (!needle) return [];
|
|
1318
|
-
return
|
|
1717
|
+
return materializeSuggestions(
|
|
1718
|
+
slashTemplates.filter((template) => compactKey(template).startsWith(needle))
|
|
1719
|
+
);
|
|
1319
1720
|
};
|
|
1320
1721
|
|
|
1321
1722
|
const getCompletionOptions = (rawInput) => {
|
|
@@ -1327,26 +1728,53 @@ export async function createChatRuntime({
|
|
|
1327
1728
|
const tokens = body.trim().split(/\s+/).filter(Boolean);
|
|
1328
1729
|
const commandPart = tokens[0] || '';
|
|
1329
1730
|
|
|
1330
|
-
const
|
|
1731
|
+
const allCommandEntries = listCommandNames();
|
|
1732
|
+
const allCommands = allCommandEntries.map((c) => c.name);
|
|
1733
|
+
for (const entry of allCommandEntries) {
|
|
1734
|
+
registerSuggestion(`/${entry.name}`, entry.description || '');
|
|
1735
|
+
}
|
|
1736
|
+
for (const template of configTemplates) {
|
|
1737
|
+
registerSuggestion(template, configSubcommandDescriptions[template] || 'config command');
|
|
1738
|
+
}
|
|
1739
|
+
for (const template of historyTemplates) registerSuggestion(template, 'history command');
|
|
1740
|
+
for (const template of modeTemplates) registerSuggestion(template, 'switch execution mode');
|
|
1741
|
+
for (const template of taskTemplates) registerSuggestion(template, 'task board command');
|
|
1742
|
+
for (const template of checkpointTemplates) registerSuggestion(template, 'checkpoint command');
|
|
1743
|
+
for (const template of specTemplates) registerSuggestion(template, 'create a spec file');
|
|
1744
|
+
for (const template of planTemplates) registerSuggestion(template, 'planning command');
|
|
1745
|
+
for (const template of agentTemplates) registerSuggestion(template, 'sub-agent command');
|
|
1746
|
+
for (const template of debugTemplates) registerSuggestion(template, 'debug command');
|
|
1747
|
+
for (const template of compactTemplates) registerSuggestion(template, 'context compaction command');
|
|
1748
|
+
registerSuggestion('/retry', 'retry the last user request');
|
|
1749
|
+
registerSuggestion('/status', 'show runtime status');
|
|
1331
1750
|
|
|
1332
1751
|
if (!commandPart) {
|
|
1333
|
-
return
|
|
1752
|
+
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
1753
|
+
allCommands.map((name) => `/${name}`),
|
|
1754
|
+
commandPriorityOrder
|
|
1755
|
+
));
|
|
1334
1756
|
}
|
|
1335
1757
|
|
|
1336
1758
|
if (tokens.length === 1 && !hasTrailingSpace) {
|
|
1337
|
-
const direct =
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1759
|
+
const direct = prioritizeByPreferredOrder(
|
|
1760
|
+
allCommands
|
|
1761
|
+
.filter((name) => name.startsWith(commandPart))
|
|
1762
|
+
.map((name) => `/${name}`),
|
|
1763
|
+
commandPriorityOrder
|
|
1764
|
+
);
|
|
1765
|
+
if (direct.length > 0) return materializeSuggestions(direct);
|
|
1341
1766
|
return matchCompactTemplates(input);
|
|
1342
1767
|
}
|
|
1343
1768
|
|
|
1344
1769
|
if (commandPart === 'config') {
|
|
1345
1770
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1346
1771
|
const sub = tokens[1] || '';
|
|
1347
|
-
return
|
|
1348
|
-
|
|
1349
|
-
|
|
1772
|
+
return materializeSuggestions(prioritizeByPreferredOrder(
|
|
1773
|
+
['set', 'get', 'list', 'reset']
|
|
1774
|
+
.filter((s) => s.startsWith(sub))
|
|
1775
|
+
.map((s) => registerSuggestion(`/config ${s}`, configSubcommandDescriptions[`/config ${s}`] || 'config command').value),
|
|
1776
|
+
configSubcommandPriority
|
|
1777
|
+
));
|
|
1350
1778
|
}
|
|
1351
1779
|
|
|
1352
1780
|
const sub = tokens[1] || '';
|
|
@@ -1354,96 +1782,98 @@ export async function createChatRuntime({
|
|
|
1354
1782
|
const keyPrefix = tokens[2] || '';
|
|
1355
1783
|
return configKeyHints
|
|
1356
1784
|
.filter((k) => k.startsWith(keyPrefix))
|
|
1357
|
-
.map((k) => `/config get ${k}
|
|
1785
|
+
.map((k) => registerSuggestion(`/config get ${k}`, describeConfigKey(k, 'get')));
|
|
1358
1786
|
}
|
|
1359
1787
|
if (sub === 'set') {
|
|
1360
1788
|
const keyPrefix = tokens[2] || '';
|
|
1361
1789
|
return configKeyHints
|
|
1362
1790
|
.filter((k) => k.startsWith(keyPrefix))
|
|
1363
|
-
.map((k) => `/config set ${k}
|
|
1791
|
+
.map((k) => registerSuggestion(`/config set ${k} `, describeConfigKey(k, 'set')));
|
|
1364
1792
|
}
|
|
1365
1793
|
|
|
1366
|
-
return configTemplates;
|
|
1794
|
+
return materializeSuggestions(configTemplates);
|
|
1367
1795
|
}
|
|
1368
1796
|
|
|
1369
1797
|
if (commandPart === 'compact') {
|
|
1370
1798
|
const joined = tokens.slice(1).join(' ');
|
|
1371
1799
|
return compactOptions
|
|
1372
1800
|
.filter((opt) => opt.includes(joined) || joined === '')
|
|
1373
|
-
.map((opt) => `/compact ${opt}
|
|
1801
|
+
.map((opt) => registerSuggestion(`/compact ${opt}`, 'context compaction command'));
|
|
1374
1802
|
}
|
|
1375
1803
|
|
|
1376
1804
|
if (commandPart === 'retry') {
|
|
1377
|
-
return ['/retry'];
|
|
1805
|
+
return [registerSuggestion('/retry', 'retry the last user request')];
|
|
1378
1806
|
}
|
|
1379
1807
|
if (commandPart === 'status') {
|
|
1380
|
-
return ['/status'];
|
|
1808
|
+
return [registerSuggestion('/status', 'show runtime status')];
|
|
1381
1809
|
}
|
|
1382
1810
|
if (commandPart === 'mode') {
|
|
1383
1811
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1384
1812
|
const sub = tokens[1] || '';
|
|
1385
1813
|
return ['normal', 'auto', 'plan']
|
|
1386
1814
|
.filter((m) => m.startsWith(sub))
|
|
1387
|
-
.map((m) => `/mode ${m}
|
|
1815
|
+
.map((m) => registerSuggestion(`/mode ${m}`, 'switch execution mode'));
|
|
1388
1816
|
}
|
|
1389
|
-
return modeTemplates;
|
|
1817
|
+
return materializeSuggestions(modeTemplates);
|
|
1390
1818
|
}
|
|
1391
1819
|
if (commandPart === 'tasks') {
|
|
1392
1820
|
if (tokens.length <= 2 && !hasTrailingSpace) {
|
|
1393
1821
|
const sub = tokens[1] || '';
|
|
1394
1822
|
return ['add', 'start', 'done', 'remove', 'rm', 'clear']
|
|
1395
1823
|
.filter((s) => s.startsWith(sub))
|
|
1396
|
-
.map((s) => `/tasks ${s}
|
|
1824
|
+
.map((s) => registerSuggestion(`/tasks ${s}`, 'task board command'));
|
|
1397
1825
|
}
|
|
1398
|
-
return taskTemplates;
|
|
1826
|
+
return materializeSuggestions(taskTemplates);
|
|
1399
1827
|
}
|
|
1400
1828
|
if (commandPart === 'checkpoint') {
|
|
1401
1829
|
if (tokens.length <= 2 && !hasTrailingSpace) {
|
|
1402
1830
|
const sub = tokens[1] || '';
|
|
1403
1831
|
return ['create', 'list', 'load']
|
|
1404
1832
|
.filter((s) => s.startsWith(sub))
|
|
1405
|
-
.map((s) => `/checkpoint ${s}
|
|
1833
|
+
.map((s) => registerSuggestion(`/checkpoint ${s}`, 'checkpoint command'));
|
|
1406
1834
|
}
|
|
1407
1835
|
if (tokens[1] === 'list') {
|
|
1408
1836
|
const hint = tokens[2] || '';
|
|
1409
|
-
return ['--all']
|
|
1837
|
+
return ['--all']
|
|
1838
|
+
.filter((v) => v.startsWith(hint))
|
|
1839
|
+
.map((v) => registerSuggestion(`/checkpoint list ${v}`, 'checkpoint command'));
|
|
1410
1840
|
}
|
|
1411
1841
|
if (tokens[1] === 'load') {
|
|
1412
1842
|
if (tokens.length >= 3) {
|
|
1413
1843
|
const hint = tokens[3] || '';
|
|
1414
1844
|
return ['--all']
|
|
1415
1845
|
.filter((v) => v.startsWith(hint))
|
|
1416
|
-
.map((v) => `/checkpoint load ${tokens[2]} ${v}
|
|
1846
|
+
.map((v) => registerSuggestion(`/checkpoint load ${tokens[2]} ${v}`, 'checkpoint command'));
|
|
1417
1847
|
}
|
|
1418
1848
|
}
|
|
1419
|
-
return checkpointTemplates;
|
|
1849
|
+
return materializeSuggestions(checkpointTemplates);
|
|
1420
1850
|
}
|
|
1421
1851
|
if (commandPart === 'spec') {
|
|
1422
|
-
return specTemplates;
|
|
1852
|
+
return materializeSuggestions(specTemplates);
|
|
1423
1853
|
}
|
|
1424
1854
|
if (commandPart === 'plan') {
|
|
1425
1855
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1426
1856
|
const sub = tokens[1] || '';
|
|
1427
1857
|
return ['auto', 'from-spec']
|
|
1428
1858
|
.filter((s) => s.startsWith(sub))
|
|
1429
|
-
.map((s) => `/plan ${s}
|
|
1859
|
+
.map((s) => registerSuggestion(`/plan ${s}`, 'planning command'));
|
|
1430
1860
|
}
|
|
1431
|
-
return planTemplates;
|
|
1861
|
+
return materializeSuggestions(planTemplates);
|
|
1432
1862
|
}
|
|
1433
1863
|
if (commandPart === 'agents') {
|
|
1434
1864
|
if (tokens.length === 1 || (tokens.length === 2 && !hasTrailingSpace)) {
|
|
1435
1865
|
const sub = tokens[1] || '';
|
|
1436
1866
|
return ['list', 'run']
|
|
1437
1867
|
.filter((s) => s.startsWith(sub))
|
|
1438
|
-
.map((s) => `/agents ${s}
|
|
1868
|
+
.map((s) => registerSuggestion(`/agents ${s}`, 'sub-agent command'));
|
|
1439
1869
|
}
|
|
1440
1870
|
if (tokens[1] === 'run') {
|
|
1441
1871
|
const rolePrefix = tokens[2] || '';
|
|
1442
1872
|
return ['planner', 'coder', 'reviewer', 'tester']
|
|
1443
1873
|
.filter((r) => r.startsWith(rolePrefix))
|
|
1444
|
-
.map((r) => `/agents run ${r}
|
|
1874
|
+
.map((r) => registerSuggestion(`/agents run ${r} `, 'sub-agent command'));
|
|
1445
1875
|
}
|
|
1446
|
-
return agentTemplates;
|
|
1876
|
+
return materializeSuggestions(agentTemplates);
|
|
1447
1877
|
}
|
|
1448
1878
|
|
|
1449
1879
|
if (commandPart === 'history') {
|
|
@@ -1460,12 +1890,13 @@ export async function createChatRuntime({
|
|
|
1460
1890
|
.filter((session) => String(session.id || '').startsWith(idPrefix))
|
|
1461
1891
|
.map((session) => ({
|
|
1462
1892
|
value: `/history resume ${session.id}`,
|
|
1463
|
-
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs
|
|
1893
|
+
display: `/history resume ${session.id} · ${Number(session.messageCount || 0)} msgs`,
|
|
1894
|
+
description: 'resume a saved session'
|
|
1464
1895
|
}));
|
|
1465
1896
|
if (dynamic.length > 0) return dynamic;
|
|
1466
|
-
return historyTemplates;
|
|
1897
|
+
return materializeSuggestions(historyTemplates);
|
|
1467
1898
|
}
|
|
1468
|
-
return historyTemplates;
|
|
1899
|
+
return materializeSuggestions(historyTemplates);
|
|
1469
1900
|
}
|
|
1470
1901
|
|
|
1471
1902
|
if (commandPart === 'debug') {
|
|
@@ -1475,9 +1906,9 @@ export async function createChatRuntime({
|
|
|
1475
1906
|
const action = tokens[2] || '';
|
|
1476
1907
|
return ['on', 'off', 'status']
|
|
1477
1908
|
.filter((v) => v.startsWith(action))
|
|
1478
|
-
.map((v) => `/debug keys ${v}
|
|
1909
|
+
.map((v) => registerSuggestion(`/debug keys ${v}`, 'keyboard debug command'));
|
|
1479
1910
|
}
|
|
1480
|
-
return debugTemplates;
|
|
1911
|
+
return materializeSuggestions(debugTemplates);
|
|
1481
1912
|
}
|
|
1482
1913
|
|
|
1483
1914
|
return [];
|
|
@@ -1519,7 +1950,7 @@ export async function createChatRuntime({
|
|
|
1519
1950
|
};
|
|
1520
1951
|
|
|
1521
1952
|
const submit = async (line, onAgentEvent) => {
|
|
1522
|
-
const activeBaseSystemPrompt = baseSystemPrompt;
|
|
1953
|
+
const activeBaseSystemPrompt = buildSystemPromptWithReplyLanguage(baseSystemPrompt, config);
|
|
1523
1954
|
const activeReplySystemPrompt = await buildSystemPromptWithSoul(baseSystemPrompt, config);
|
|
1524
1955
|
try {
|
|
1525
1956
|
await appendInputHistory(line);
|