codemini-cli 0.1.1 → 0.1.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.12",
4
4
  "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
5
  "keywords": [
6
6
  "cli",
@@ -498,7 +498,8 @@ function buildAutoPlanSystemSummary(auto) {
498
498
  const lines = [
499
499
  statusTitle,
500
500
  `File: ${auto.filePath}`,
501
- `Summary: ${auto.summary || '-'}`,
501
+ `Plan Summary: ${auto.summary || '-'}`,
502
+ `Final Summary: ${auto.finalSummary || auto.summary || '-'}`,
502
503
  `Steps: ${auto.steps.length} total`,
503
504
  `Completed: ${auto.completedCount}`,
504
505
  `Warnings: ${auto.warningCount}`,
@@ -513,6 +514,95 @@ function buildAutoPlanSystemSummary(auto) {
513
514
  return lines.join('\n');
514
515
  }
515
516
 
517
+ function buildAutoPlanFinalSummaryUserPrompt({ goal, autoPlan, runItems, planningError }) {
518
+ const lines = [];
519
+ lines.push('Create a final execution summary for an auto-generated implementation/test plan.');
520
+ lines.push('Keep it concise, high-signal, and outcome-focused.');
521
+ lines.push('Include: overall result, what was verified, what is still pending, and the best next action.');
522
+ lines.push('Use plain text only. Do not use markdown fences.');
523
+ lines.push('');
524
+ lines.push(`Goal: ${goal}`);
525
+ lines.push(`Plan Summary: ${autoPlan?.summary || `Auto plan for: ${goal}`}`);
526
+ if (planningError) {
527
+ lines.push(`Planning Error: ${planningError}`);
528
+ }
529
+ lines.push('');
530
+ lines.push('Executed Steps:');
531
+ runItems.forEach((item, idx) => {
532
+ lines.push(`${idx + 1}. [${item.role}] ${item.title}`);
533
+ if (item.failed) {
534
+ lines.push(`Status: failed`);
535
+ } else if (item.warning) {
536
+ lines.push(`Status: warning`);
537
+ } else {
538
+ lines.push(`Status: completed`);
539
+ }
540
+ if (item.error) {
541
+ lines.push(`Error: ${item.error}`);
542
+ }
543
+ if (item.warning) {
544
+ lines.push(`Warning: ${item.warning}`);
545
+ }
546
+ lines.push(`Output: ${trimInlineText(item.output || '(empty)', 500)}`);
547
+ if (Array.isArray(item.artifactPaths) && item.artifactPaths.length > 0) {
548
+ lines.push(`Artifacts: ${item.artifactPaths.slice(0, 5).join(', ')}`);
549
+ }
550
+ lines.push('');
551
+ });
552
+ return lines.join('\n').trim();
553
+ }
554
+
555
+ async function buildAutoPlanFinalSummary({
556
+ goal,
557
+ autoPlan,
558
+ runItems,
559
+ planningError,
560
+ config,
561
+ model,
562
+ systemPrompt
563
+ }) {
564
+ const fallbackParts = [];
565
+ if (runItems.some((item) => item.failed || item.error)) {
566
+ fallbackParts.push('Execution finished with failed steps.');
567
+ } else if (runItems.some((item) => item.warning)) {
568
+ fallbackParts.push('Execution finished with warnings.');
569
+ } else {
570
+ fallbackParts.push('Execution finished successfully.');
571
+ }
572
+ const verifiedTitles = runItems.filter((item) => !item.failed).map((item) => item.title);
573
+ const pendingTitles = runItems.filter((item) => item.failed || item.warning).map((item) => item.title);
574
+ if (verifiedTitles.length > 0) {
575
+ fallbackParts.push(`Completed: ${verifiedTitles.slice(0, 4).join(', ')}.`);
576
+ }
577
+ if (pendingTitles.length > 0) {
578
+ fallbackParts.push(`Needs follow-up: ${pendingTitles.slice(0, 4).join(', ')}.`);
579
+ }
580
+ const fallbackSummary = fallbackParts.join(' ');
581
+
582
+ try {
583
+ const result = await createChatCompletion({
584
+ baseUrl: config.gateway.base_url,
585
+ apiKey: config.gateway.api_key,
586
+ model: model || config.model.name,
587
+ messages: [
588
+ {
589
+ role: 'system',
590
+ content: `${systemPrompt}\nYou are writing the final execution summary for a completed auto plan. Focus on closure, verification status, and the next action.`
591
+ },
592
+ {
593
+ role: 'user',
594
+ content: buildAutoPlanFinalSummaryUserPrompt({ goal, autoPlan, runItems, planningError })
595
+ }
596
+ ],
597
+ timeoutMs: config.gateway.timeout_ms || 90000,
598
+ maxRetries: config.gateway.max_retries ?? 2
599
+ });
600
+ return trimInlineText(result.text || '', 600) || fallbackSummary;
601
+ } catch {
602
+ return fallbackSummary;
603
+ }
604
+ }
605
+
516
606
  async function writeMarkdownInCoderDir(subDir, title, body, fallbackName, sessionId) {
517
607
  const parts = [process.cwd(), '.coder', subDir];
518
608
  if (sessionId) parts.push(String(sessionId));
@@ -1050,12 +1140,13 @@ async function buildAutoPlanAndRun({
1050
1140
  }
1051
1141
 
1052
1142
  const runItems = [];
1143
+ const totalPlanSteps = autoPlan.steps.length + 1;
1053
1144
  for (let i = 0; i < autoPlan.steps.length; i += 1) {
1054
1145
  const step = autoPlan.steps[i];
1055
1146
  if (onAgentEvent) {
1056
1147
  onAgentEvent({
1057
1148
  type: 'assistant:delta',
1058
- text: `\n[plan] Step ${i + 1}/${autoPlan.steps.length} -> ${step.role}: ${step.title}\n`
1149
+ text: `\n[plan] Step ${i + 1}/${totalPlanSteps} -> ${step.role}: ${step.title}\n`
1059
1150
  });
1060
1151
  }
1061
1152
  try {
@@ -1106,11 +1197,30 @@ async function buildAutoPlanAndRun({
1106
1197
  const warningItems = runItems.filter((s) => !s.failed && s.warning);
1107
1198
  const completedItems = runItems.filter((s) => !s.failed);
1108
1199
 
1200
+ if (onAgentEvent) {
1201
+ onAgentEvent({
1202
+ type: 'assistant:delta',
1203
+ text: `\n[plan] Step ${totalPlanSteps}/${totalPlanSteps} -> summarizer: Final summary\n`
1204
+ });
1205
+ }
1206
+ const finalSummary = await buildAutoPlanFinalSummary({
1207
+ goal,
1208
+ autoPlan,
1209
+ runItems,
1210
+ planningError,
1211
+ config,
1212
+ model,
1213
+ systemPrompt
1214
+ });
1215
+
1109
1216
  const lines = [];
1110
1217
  lines.push(`# Auto Plan: ${goal}`);
1111
1218
  lines.push('');
1112
1219
  lines.push(`## Summary`);
1113
1220
  lines.push(autoPlan.summary || `Auto plan for: ${goal}`);
1221
+ lines.push('');
1222
+ lines.push('## Final Summary');
1223
+ lines.push(finalSummary || '(empty)');
1114
1224
  if (planningError) {
1115
1225
  lines.push('');
1116
1226
  lines.push(`Planning Error: ${planningError}`);
@@ -1152,6 +1262,7 @@ async function buildAutoPlanAndRun({
1152
1262
  return {
1153
1263
  filePath,
1154
1264
  summary: autoPlan.summary,
1265
+ finalSummary,
1155
1266
  steps: autoPlan.steps,
1156
1267
  completedCount: completedItems.length,
1157
1268
  warningCount: warningItems.length,
@@ -36,11 +36,38 @@ function isMiniMaxModel(model) {
36
36
  return String(model || '').toLowerCase().includes('minimax');
37
37
  }
38
38
 
39
+ function sanitizeMiniMaxMessages(messages) {
40
+ const source = Array.isArray(messages) ? messages : [];
41
+ const out = [];
42
+ let seenNonSystem = false;
43
+ let keptLeadingSystem = false;
44
+
45
+ for (const message of source) {
46
+ if (!message || typeof message !== 'object') continue;
47
+ if (message.role === 'system') {
48
+ if (!seenNonSystem && !keptLeadingSystem) {
49
+ out.push(message);
50
+ keptLeadingSystem = true;
51
+ } else {
52
+ out.push({
53
+ role: 'user',
54
+ content: `[system-note]\n${extractTextContent(message.content)}`
55
+ });
56
+ }
57
+ continue;
58
+ }
59
+ seenNonSystem = true;
60
+ out.push(message);
61
+ }
62
+
63
+ return out;
64
+ }
65
+
39
66
  function buildPayload({ model, temperature, messages, tools, stream = false }) {
40
67
  const payload = {
41
68
  model,
42
69
  temperature,
43
- messages
70
+ messages: isMiniMaxModel(model) ? sanitizeMiniMaxMessages(messages) : messages
44
71
  };
45
72
  if (stream) {
46
73
  payload.stream = true;
@@ -546,6 +546,161 @@ function renderTextLine(msg, line, idx, color) {
546
546
  );
547
547
  }
548
548
 
549
+ export function parseAutoPlanSummaryMessage(text) {
550
+ const raw = String(text || '').trim();
551
+ if (!/^Auto plan finished\b/i.test(raw)) return null;
552
+
553
+ const lines = raw
554
+ .split('\n')
555
+ .map((line) => line.trim())
556
+ .filter(Boolean);
557
+ const parsed = {
558
+ statusTitle: lines[0] || '',
559
+ filePath: '',
560
+ planSummary: '',
561
+ finalSummary: '',
562
+ stepsTotal: '',
563
+ completed: '',
564
+ warnings: '',
565
+ failed: '',
566
+ warningSteps: '',
567
+ failedSteps: ''
568
+ };
569
+
570
+ for (const line of lines.slice(1)) {
571
+ if (line.startsWith('File: ')) parsed.filePath = line.slice('File: '.length).trim();
572
+ else if (line.startsWith('Plan Summary: ')) parsed.planSummary = line.slice('Plan Summary: '.length).trim();
573
+ else if (line.startsWith('Final Summary: ')) parsed.finalSummary = line.slice('Final Summary: '.length).trim();
574
+ else if (line.startsWith('Steps: ')) parsed.stepsTotal = line.slice('Steps: '.length).trim();
575
+ else if (line.startsWith('Completed: ')) parsed.completed = line.slice('Completed: '.length).trim();
576
+ else if (line.startsWith('Warnings: ')) parsed.warnings = line.slice('Warnings: '.length).trim();
577
+ else if (line.startsWith('Failed: ')) parsed.failed = line.slice('Failed: '.length).trim();
578
+ else if (line.startsWith('Warning steps: ')) parsed.warningSteps = line.slice('Warning steps: '.length).trim();
579
+ else if (line.startsWith('Failed steps: ')) parsed.failedSteps = line.slice('Failed steps: '.length).trim();
580
+ }
581
+
582
+ return parsed;
583
+ }
584
+
585
+ function PlanSummaryBubble({ msg, copy }) {
586
+ const theme = roleStyle(msg.label);
587
+ const summary = msg.planSummary || parseAutoPlanSummaryMessage(msg.text);
588
+ if (!summary) return null;
589
+
590
+ const statusColor =
591
+ Number(summary.failed || 0) > 0 ? 'redBright' : Number(summary.warnings || 0) > 0 ? 'yellowBright' : 'greenBright';
592
+ const isEnglish = copy?.roleLabels?.system === 'SYSTEM';
593
+ const labels = isEnglish
594
+ ? {
595
+ conclusion: 'Conclusion',
596
+ plan: 'Plan',
597
+ warnings: 'Warnings',
598
+ failed: 'Failed',
599
+ file: 'File',
600
+ steps: 'steps',
601
+ done: 'done',
602
+ warn: 'warn',
603
+ fail: 'fail'
604
+ }
605
+ : {
606
+ conclusion: '结论',
607
+ plan: '计划',
608
+ warnings: '警告',
609
+ failed: '失败',
610
+ file: '文件',
611
+ steps: '步骤',
612
+ done: '完成',
613
+ warn: '警告',
614
+ fail: '失败'
615
+ };
616
+ const metaItems = [
617
+ summary.stepsTotal ? `${labels.steps} ${summary.stepsTotal}` : '',
618
+ summary.completed ? `${labels.done} ${summary.completed}` : '',
619
+ summary.warnings ? `${labels.warn} ${summary.warnings}` : '',
620
+ summary.failed ? `${labels.fail} ${summary.failed}` : ''
621
+ ].filter(Boolean);
622
+ const shortFile = summary.filePath ? trimText(summary.filePath, 96) : '';
623
+
624
+ return h(
625
+ Box,
626
+ { marginBottom: 1, flexDirection: 'row' },
627
+ h(Box, { width: 2 }, h(Text, { color: theme.accent }, '│')),
628
+ h(
629
+ Box,
630
+ {
631
+ flexDirection: 'column',
632
+ borderStyle: 'round',
633
+ borderColor: theme.border,
634
+ paddingX: 1,
635
+ paddingY: 0,
636
+ width: '100%'
637
+ },
638
+ h(
639
+ Box,
640
+ { justifyContent: 'space-between', marginBottom: summary.finalSummary ? 1 : 0 },
641
+ h(
642
+ Box,
643
+ null,
644
+ h(Text, { color: theme.badgeText, backgroundColor: theme.badgeBg }, ` ${messageLabel(msg.label, copy)} `),
645
+ h(Text, { color: 'gray' }, ' '),
646
+ h(Text, { color: statusColor }, summary.statusTitle)
647
+ ),
648
+ h(Text, { color: theme.chrome }, ' ')
649
+ ),
650
+ summary.finalSummary
651
+ ? h(
652
+ Box,
653
+ { marginBottom: summary.planSummary || metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
654
+ h(Text, { color: statusColor }, labels.conclusion),
655
+ h(Text, { color: 'white' }, summary.finalSummary)
656
+ )
657
+ : null,
658
+ summary.planSummary
659
+ ? h(
660
+ Box,
661
+ { marginBottom: metaItems.length > 0 || summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
662
+ h(Text, { color: 'cyanBright' }, labels.plan),
663
+ h(Text, { color: 'gray' }, summary.planSummary)
664
+ )
665
+ : null,
666
+ metaItems.length > 0
667
+ ? h(
668
+ Box,
669
+ { marginBottom: summary.warningSteps || summary.failedSteps || shortFile ? 1 : 0 },
670
+ ...metaItems.flatMap((item, idx) => [
671
+ idx > 0 ? h(Text, { key: `sep-${idx}`, color: 'gray' }, ' ') : null,
672
+ h(Text, { key: `meta-${idx}`, color: 'gray' }, item)
673
+ ])
674
+ )
675
+ : null,
676
+ summary.warningSteps
677
+ ? h(
678
+ Box,
679
+ { marginBottom: summary.failedSteps || shortFile ? 1 : 0, flexDirection: 'column' },
680
+ h(Text, { color: 'yellowBright' }, labels.warnings),
681
+ h(Text, { color: 'gray' }, summary.warningSteps)
682
+ )
683
+ : null,
684
+ summary.failedSteps
685
+ ? h(
686
+ Box,
687
+ { marginBottom: shortFile ? 1 : 0, flexDirection: 'column' },
688
+ h(Text, { color: 'redBright' }, labels.failed),
689
+ h(Text, { color: 'gray' }, summary.failedSteps)
690
+ )
691
+ : null,
692
+ shortFile
693
+ ? h(
694
+ Box,
695
+ { flexDirection: 'column' },
696
+ h(Text, { color: 'gray' }, labels.file),
697
+ h(Text, { color: 'gray' }, shortFile)
698
+ )
699
+ : null
700
+ )
701
+ );
702
+ }
703
+
549
704
  const BUBBLE_CHROME_ROWS = 4;
550
705
 
551
706
  function charDisplayWidth(ch) {
@@ -729,6 +884,9 @@ function getSuggestionDisplay(item) {
729
884
  }
730
885
 
731
886
  function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, contentWidth = 72, copy }) {
887
+ if (msg?.planSummary || parseAutoPlanSummaryMessage(msg?.text)) {
888
+ return h(PlanSummaryBubble, { msg, copy });
889
+ }
732
890
  const theme = roleStyle(msg.label);
733
891
  const allRows = buildMessageRows(msg, showToolDetails, contentWidth);
734
892
  const start = rowWindow ? Math.max(0, rowWindow.start || 0) : 0;
@@ -1338,9 +1496,16 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1338
1496
  }
1339
1497
  return;
1340
1498
  }
1499
+ const parsedPlanSummary = result.type === 'system' ? parseAutoPlanSummaryMessage(result.text || '') : null;
1341
1500
  setMessages((prev) => [
1342
1501
  ...prev,
1343
- { id: nextId(), label: 'system', text: result.text || '', color: 'yellowBright' }
1502
+ {
1503
+ id: nextId(),
1504
+ label: 'system',
1505
+ text: result.text || '',
1506
+ color: 'yellowBright',
1507
+ ...(parsedPlanSummary ? { planSummary: parsedPlanSummary } : {})
1508
+ }
1344
1509
  ]);
1345
1510
  };
1346
1511