codemini-cli 0.1.13 → 0.1.15

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.
@@ -316,6 +316,11 @@ function getActivityDisplayParts(activity) {
316
316
  read_file: 'Read',
317
317
  write_file: 'Write',
318
318
  run_command: 'Command',
319
+ start_service: 'Service',
320
+ list_services: 'Service',
321
+ get_service_status: 'Service',
322
+ get_service_logs: 'Service',
323
+ stop_service: 'Service',
319
324
  list_files: 'Glob',
320
325
  create_task: 'Task',
321
326
  update_task: 'Task'
@@ -357,6 +362,13 @@ function describeToolActivity(name, copy, { done = false, blocked = false } = {}
357
362
  ? `${copy.toolActivity.doneCommand}: ${safeTarget || 'run_command'}`
358
363
  : `${copy.toolActivity.doingCommand}: ${safeTarget || 'run_command'}`;
359
364
  }
365
+ if (base === 'start_service' || base === 'list_services' || base === 'get_service_status' || base === 'get_service_logs' || base === 'stop_service') {
366
+ return blocked
367
+ ? `${copy.toolActivity.blocked}: ${safeTarget || base}`
368
+ : done
369
+ ? `${copy.toolActivity.doneGeneric}: ${safeTarget || base}`
370
+ : `${copy.toolActivity.doingGeneric}: ${safeTarget || base}`;
371
+ }
360
372
  if (base === 'create_task') {
361
373
  return blocked ? `${copy.toolActivity.blocked}: create_task` : done ? copy.toolActivity.doneCreateTask : copy.toolActivity.doingCreateTask;
362
374
  }
@@ -472,9 +484,13 @@ function PlanStrip({ planState, copy }) {
472
484
  ...planState.steps.slice(-4).map((step, idx) =>
473
485
  h(
474
486
  Box,
475
- { key: `plan-step-${idx}` },
487
+ { key: `plan-step-${idx}`, marginTop: idx === 0 ? 0 : 1 },
476
488
  h(Text, { color: step.status === 'active' ? 'cyanBright' : step.status === 'failed' ? 'redBright' : 'gray' }, `${step.status === 'active' ? '>' : step.status === 'failed' ? 'x' : '·'} `),
477
- h(Text, { color: step.status === 'active' ? 'white' : step.status === 'failed' ? 'redBright' : 'gray' }, `${step.index}/${step.total} ${step.role}: ${step.title}`)
489
+ h(Text, { color: step.status === 'active' ? 'yellowBright' : step.status === 'failed' ? 'redBright' : 'gray' }, `${step.index}/${step.total}`),
490
+ h(Text, { color: 'gray' }, ' '),
491
+ h(Text, { color: step.status === 'active' ? 'magentaBright' : step.status === 'failed' ? 'redBright' : 'gray' }, String(step.role || 'agent').toUpperCase()),
492
+ h(Text, { color: 'gray' }, ' '),
493
+ h(Text, { color: step.status === 'active' ? 'white' : step.status === 'failed' ? 'redBright' : 'gray' }, step.title)
478
494
  )
479
495
  )
480
496
  )
@@ -583,6 +599,39 @@ export function parseAutoPlanSummaryMessage(text) {
583
599
  return parsed;
584
600
  }
585
601
 
602
+ export function parsePlanProgressLine(text) {
603
+ const raw = String(text || '').trim();
604
+ const match = raw.match(/^\[plan\]\s+Step\s+(\d+)\/(\d+)\s+->\s+([^:]+):\s+(.+)$/i);
605
+ if (!match) return null;
606
+ return {
607
+ current: Number(match[1]),
608
+ total: Number(match[2]),
609
+ role: String(match[3] || '').trim(),
610
+ title: String(match[4] || '').trim()
611
+ };
612
+ }
613
+
614
+ export function injectPlanStateMessage(messages, planState, activeUserMessageId, activeAssistantId) {
615
+ const source = Array.isArray(messages) ? messages : [];
616
+ if (!planState || !planState.total) return source;
617
+ const synthetic = {
618
+ id: `plan-state-${planState.current}-${planState.total}-${planState.role || 'agent'}`,
619
+ label: 'system',
620
+ planStrip: true,
621
+ planState
622
+ };
623
+ const withNoPlanStrip = source.filter((message) => !message?.planStrip);
624
+ const userIdx = withNoPlanStrip.findIndex((message) => message.id === activeUserMessageId);
625
+ if (userIdx !== -1) {
626
+ return [...withNoPlanStrip.slice(0, userIdx + 1), synthetic, ...withNoPlanStrip.slice(userIdx + 1)];
627
+ }
628
+ const assistantIdx = withNoPlanStrip.findIndex((message) => message.id === activeAssistantId);
629
+ if (assistantIdx !== -1) {
630
+ return [...withNoPlanStrip.slice(0, assistantIdx), synthetic, ...withNoPlanStrip.slice(assistantIdx)];
631
+ }
632
+ return [...withNoPlanStrip, synthetic];
633
+ }
634
+
586
635
  function PlanSummaryBubble({ msg, copy }) {
587
636
  const theme = roleStyle(msg.label);
588
637
  const summary = msg.planSummary || parseAutoPlanSummaryMessage(msg.text);
@@ -760,6 +809,114 @@ function pushWrappedRow(rows, baseRow, contentWidth) {
760
809
  });
761
810
  }
762
811
 
812
+ function isActivityRow(row) {
813
+ return row?.kind === 'activity' || row?.kind === 'activity-summary';
814
+ }
815
+
816
+ function isBlankTextRow(row) {
817
+ return row?.kind === 'text' && String(row?.text || '').trim() === '';
818
+ }
819
+
820
+ export function normalizeActivitySpacingRows(inputRows) {
821
+ const rows = Array.isArray(inputRows) ? inputRows : [];
822
+ const normalized = [];
823
+
824
+ for (let index = 0; index < rows.length; index += 1) {
825
+ const row = rows[index];
826
+ const prev = normalized.at(-1);
827
+ const next = rows[index + 1];
828
+
829
+ if (isBlankTextRow(row)) {
830
+ let lookahead = index + 1;
831
+ while (lookahead < rows.length && isBlankTextRow(rows[lookahead])) {
832
+ lookahead += 1;
833
+ }
834
+ if (isActivityRow(rows[lookahead])) {
835
+ continue;
836
+ }
837
+ }
838
+
839
+ if (isBlankTextRow(row) && isActivityRow(next)) {
840
+ continue;
841
+ }
842
+
843
+ normalized.push(row);
844
+
845
+ if (isActivityRow(row) && !isActivityRow(next) && next) {
846
+ const last = normalized.at(-1);
847
+ if (!isBlankTextRow(last) && !(next.kind === 'status')) {
848
+ normalized.push({
849
+ kind: 'text',
850
+ text: ' ',
851
+ color: 'white'
852
+ });
853
+ }
854
+ }
855
+
856
+ if (isBlankTextRow(row) && isBlankTextRow(prev)) {
857
+ normalized.pop();
858
+ }
859
+ }
860
+
861
+ return normalized;
862
+ }
863
+
864
+ function isReadActivityName(name) {
865
+ const parsed = parseToolDisplayName(name);
866
+ return parsed.base === 'read_file' || parsed.base === 'Read';
867
+ }
868
+
869
+ function isIgnorableSegmentAfterRead(item, activityType, activityName) {
870
+ if (!item) return true;
871
+ if (item.type === 'text') {
872
+ return String(item.text || '').trim() === '';
873
+ }
874
+ return (item.type || 'tool') === activityType && item.name === activityName;
875
+ }
876
+
877
+ export function findActivityUpdateIndex(items, toolEvent) {
878
+ const source = Array.isArray(items) ? items : [];
879
+ const activityType = toolEvent?.type || 'tool';
880
+ const byId = toolEvent?.id
881
+ ? source.findIndex((item) => item.type === activityType && item.id && item.id === toolEvent.id)
882
+ : -1;
883
+ if (byId !== -1) return byId;
884
+
885
+ const byNameRunning = source.findIndex(
886
+ (item) => (item.type || 'tool') === activityType && item.name === toolEvent?.name && item.status !== 'done'
887
+ );
888
+ if (byNameRunning !== -1) return byNameRunning;
889
+
890
+ if (isReadActivityName(toolEvent?.name)) {
891
+ for (let index = source.length - 1; index >= 0; index -= 1) {
892
+ const item = source[index];
893
+ if ((item?.type || 'tool') !== activityType || item?.name !== toolEvent?.name) continue;
894
+ const trailing = source.slice(index + 1);
895
+ if (trailing.every((entry) => isIgnorableSegmentAfterRead(entry, activityType, toolEvent?.name))) {
896
+ return index;
897
+ }
898
+ }
899
+ }
900
+
901
+ return -1;
902
+ }
903
+
904
+ export function mergeActivitySummary(previousSummary, nextSummary, activityName) {
905
+ const prev = String(previousSummary || '').trim();
906
+ const next = String(nextSummary || '').trim();
907
+ if (!next) return prev;
908
+ if (!prev) return next;
909
+ if (!isReadActivityName(activityName) || prev === next) return next;
910
+
911
+ const lines = [];
912
+ for (const line of `${prev}\n${next}`.split('\n')) {
913
+ const trimmed = String(line || '').trim();
914
+ if (!trimmed) continue;
915
+ if (!lines.includes(trimmed)) lines.push(trimmed);
916
+ }
917
+ return lines.join('\n');
918
+ }
919
+
763
920
  function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
764
921
  const rows = [];
765
922
  const pushTextRows = (text) => {
@@ -767,6 +924,17 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
767
924
  let codeFence = false;
768
925
  for (const line of lines) {
769
926
  const trimmed = line.trim();
927
+ const planProgress = parsePlanProgressLine(trimmed);
928
+ if (planProgress) {
929
+ rows.push({
930
+ kind: 'plan-progress',
931
+ current: planProgress.current,
932
+ total: planProgress.total,
933
+ role: planProgress.role,
934
+ title: trimText(planProgress.title, Math.max(12, contentWidth - 18))
935
+ });
936
+ continue;
937
+ }
770
938
  if (trimmed.startsWith('```')) {
771
939
  codeFence = !codeFence;
772
940
  continue;
@@ -843,7 +1011,7 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
843
1011
  );
844
1012
  }
845
1013
 
846
- return rows;
1014
+ return normalizeActivitySpacingRows(rows);
847
1015
  }
848
1016
 
849
1017
 
@@ -945,6 +1113,13 @@ export function moveSuggestionSelection(currentIndex, itemCount, direction, page
945
1113
  }
946
1114
 
947
1115
  function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, contentWidth = 72, copy }) {
1116
+ if (msg?.planStrip) {
1117
+ return h(
1118
+ Box,
1119
+ { marginBottom: 1 },
1120
+ h(PlanStrip, { planState: msg.planState, copy })
1121
+ );
1122
+ }
948
1123
  if (msg?.planSummary || parseAutoPlanSummaryMessage(msg?.text)) {
949
1124
  return h(PlanSummaryBubble, { msg, copy });
950
1125
  }
@@ -991,6 +1166,18 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
991
1166
  h(Text, { color: 'gray' }, `└ ${row.text}`)
992
1167
  );
993
1168
  }
1169
+ if (row.kind === 'plan-progress') {
1170
+ return h(
1171
+ Box,
1172
+ { key: `row-plan-progress-${msg.id}-${idx}`, marginTop: 1, marginBottom: 1 },
1173
+ h(Text, { color: 'cyanBright' }, '[plan] '),
1174
+ h(Text, { color: 'yellowBright' }, `Step ${row.current}/${row.total}`),
1175
+ h(Text, { color: 'gray' }, ' -> '),
1176
+ h(Text, { color: 'magentaBright' }, String(row.role || 'agent').toUpperCase()),
1177
+ h(Text, { color: 'gray' }, ': '),
1178
+ h(Text, { color: 'white' }, row.title)
1179
+ );
1180
+ }
994
1181
  if (row.kind === 'status') {
995
1182
  const dots = '.'.repeat((loaderTick % 3) + 1);
996
1183
  const phase = msg.phase;
@@ -1456,13 +1643,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1456
1643
  if (m.id !== targetId) return m;
1457
1644
  const toolCalls = Array.isArray(m.toolCalls) ? [...m.toolCalls] : [];
1458
1645
  const activityType = toolEvent.type || 'tool';
1459
- const byId = toolEvent.id
1460
- ? toolCalls.findIndex((t) => t.type === activityType && t.id && t.id === toolEvent.id)
1461
- : -1;
1462
- const byNameRunning = toolCalls.findIndex(
1463
- (t) => (t.type || 'tool') === activityType && t.name === toolEvent.name && t.status !== 'done'
1464
- );
1465
- const idx = byId !== -1 ? byId : byNameRunning;
1646
+ const idx = findActivityUpdateIndex(toolCalls, toolEvent);
1466
1647
 
1467
1648
  if (idx === -1) {
1468
1649
  toolCalls.push({
@@ -1480,17 +1661,13 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1480
1661
  id: toolEvent.id || toolCalls[idx].id,
1481
1662
  status: toolEvent.status,
1482
1663
  ...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
1483
- ...(toolEvent.summary ? { summary: toolEvent.summary } : {})
1664
+ ...(toolEvent.summary
1665
+ ? { summary: mergeActivitySummary(toolCalls[idx].summary, toolEvent.summary, toolEvent.name) }
1666
+ : {})
1484
1667
  };
1485
1668
  }
1486
1669
  const segments = Array.isArray(m.segments) ? [...m.segments] : [];
1487
- const bySegmentId = toolEvent.id
1488
- ? segments.findIndex((segment) => segment.type === activityType && segment.id === toolEvent.id)
1489
- : -1;
1490
- const bySegmentName = segments.findIndex(
1491
- (segment) => segment.type === activityType && segment.name === toolEvent.name && segment.status !== 'done'
1492
- );
1493
- const segmentIdx = bySegmentId !== -1 ? bySegmentId : bySegmentName;
1670
+ const segmentIdx = findActivityUpdateIndex(segments, toolEvent);
1494
1671
  const patch = {
1495
1672
  type: activityType,
1496
1673
  id: toolEvent.id || '',
@@ -1504,7 +1681,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
1504
1681
  } else {
1505
1682
  segments[segmentIdx] = {
1506
1683
  ...segments[segmentIdx],
1507
- ...patch
1684
+ ...patch,
1685
+ ...(toolEvent.summary
1686
+ ? { summary: mergeActivitySummary(segments[segmentIdx].summary, toolEvent.summary, toolEvent.name) }
1687
+ : {})
1508
1688
  };
1509
1689
  }
1510
1690
  return { ...m, toolCalls, segments };
@@ -2192,16 +2372,21 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2192
2372
  const hasConversationStarted = messages.some((m) =>
2193
2373
  ['you', 'coder', 'pending', 'error'].includes(m.label)
2194
2374
  );
2195
- const visibleMessages = hasConversationStarted
2375
+ const baseVisibleMessages = hasConversationStarted
2196
2376
  ? messages.filter((m) => !(m.label === 'system' && m.text === startupHint))
2197
2377
  : messages;
2378
+ const visibleMessages = injectPlanStateMessage(
2379
+ baseVisibleMessages,
2380
+ planState,
2381
+ activeUserMessageIdRef.current,
2382
+ activeAssistantIdRef.current
2383
+ );
2198
2384
 
2199
2385
  return h(
2200
2386
  Box,
2201
2387
  { flexDirection: 'column' },
2202
2388
  h(Header, { sessionId: displaySessionId, model: displayModel, shellName }),
2203
2389
  h(RuntimeStrip, { busy, runtimeStatus, loaderTick, copy }),
2204
- h(PlanStrip, { planState, copy }),
2205
2390
  h(MessageList, {
2206
2391
  messages: visibleMessages,
2207
2392
  loaderTick,
@@ -1,41 +0,0 @@
1
- ---
2
- name: executing-plan-lite
3
- description: Lightweight plan execution skill for 30B-class models. Execute the plan in small verified steps with narrow context and frequent checks.
4
- version: 0.1.0
5
- ---
6
-
7
- Use this skill when a direction is chosen and the next job is to carry out implementation reliably.
8
-
9
- Rules:
10
-
11
- 1. Execute the plan in small verified steps.
12
- Take one bounded step at a time. Avoid mixing planning, implementation, and verification into one big jump.
13
-
14
- 2. Keep the active context narrow.
15
- Work from the smallest relevant file set and recent evidence. If needed, use sub-agents for independent subtasks.
16
-
17
- 3. Search before editing.
18
- Use `rg` to locate code, inspect the smallest useful context, then edit.
19
-
20
- 4. Verify after each meaningful change.
21
- Run the most relevant test or command before claiming success.
22
-
23
- 5. Report progress briefly.
24
- Summarize what changed, what was verified, and what remains.
25
-
26
- Suggested flow:
27
- - identify the next step
28
- - search and inspect
29
- - edit
30
- - verify
31
- - either continue or stop at a clear checkpoint
32
-
33
- Use sub-agents when:
34
- - the task can be split cleanly
35
- - the write scope is disjoint
36
- - the result can be reviewed independently
37
-
38
- Avoid:
39
- - broad refactors without a reason
40
- - carrying full history into each step
41
- - declaring completion without verification