@miphamai/cli 0.16.3 → 0.16.5

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": "@miphamai/cli",
3
- "version": "0.16.3",
3
+ "version": "0.16.5",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
package/src/ui/app.tsx CHANGED
@@ -59,16 +59,21 @@ interface AgentProgress {
59
59
  // Version is read fresh from package.json at startup via runApp prop
60
60
  // (bypasses Bun module caching after npm update)
61
61
 
62
+ // Cycle order: Claude Code modes first (manual → accept edits → bypass),
63
+ // then Mipham-specific modes (plan → auto → dont ask).
62
64
  const PERMISSION_MODES: PermissionMode[] = [
63
65
  'default',
64
66
  'acceptEdits',
67
+ 'bypassPermissions',
65
68
  'plan',
66
69
  'auto',
67
70
  'dontAsk',
68
- 'bypassPermissions',
69
71
  ]
72
+ // Labels aligned with Claude Code terminology: describe behavior, not capability.
73
+ // Claude Code modes: manual mode → accept edits on → bypass
74
+ // Mipham extends with 3 extra modes (plan, auto, dontAsk) for finer control.
70
75
  const PERMISSION_LABELS: Record<PermissionMode, string> = {
71
- default: 'reads free',
76
+ default: 'manual mode',
72
77
  acceptEdits: 'accept edits on',
73
78
  plan: 'plan mode · read-only',
74
79
  auto: 'auto mode',
@@ -348,18 +353,10 @@ export function App({
348
353
 
349
354
  try {
350
355
  for await (const chunk of engine.process(input, controller.signal)) {
351
- // Show thinking indicator when model is reasoning (DeepSeek V4 thinking mode)
352
- if (chunk.reasoning_content && turnContent === '') {
353
- setMessages((prev) => {
354
- const updated = [...prev]
355
- const last = updated[updated.length - 1]
356
- if (last?.role === 'assistant' && last.content === '') {
357
- last.content = '💭 思考中…'
358
- } else if (!last || last.role !== 'assistant' || last.content !== '💭 思考中…') {
359
- updated.push({ role: 'assistant', content: '💭 思考中…' })
360
- }
361
- return updated
362
- })
356
+ // Reasoning content (DeepSeek V4 thinking mode) — silently consumed,
357
+ // not shown to user to avoid noise.
358
+ if (chunk.reasoning_content) {
359
+ continue
363
360
  }
364
361
 
365
362
  if (chunk.type === 'text' && chunk.content) {
@@ -367,17 +364,10 @@ export function App({
367
364
  if (isNewTurn) {
368
365
  turnContent = chunk.content
369
366
  isNewTurn = false
370
- setMessages((prev) => {
371
- const updated = [...prev]
372
- const last = updated[updated.length - 1]
373
- // Replace "thinking" placeholder with real content
374
- if (last?.role === 'assistant' && last.content === '💭 思考中…') {
375
- last.content = turnContent
376
- } else {
377
- updated.push({ role: 'assistant', content: turnContent })
378
- }
379
- return updated
380
- })
367
+ setMessages((prev) => [
368
+ ...prev,
369
+ { role: 'assistant', content: turnContent },
370
+ ])
381
371
  } else {
382
372
  turnContent += chunk.content
383
373
  setMessages((prev) => {
@@ -425,9 +415,10 @@ export function App({
425
415
  setAgentProgress(null)
426
416
  // Sync background agents (e.g. Agent tool may have spawned them)
427
417
  syncBgAgents()
428
- // Show tool result as a compact indented line
418
+ // Only show tool results with meaningful content.
419
+ // Skip empty results, "(no matches)", and other noise.
429
420
  const output = chunk.content ? String(chunk.content).trim() : ''
430
- if (output) {
421
+ if (output && output !== '(no matches)' && output.length > 20) {
431
422
  const firstLine = output.split('\n')[0]!.slice(0, 200)
432
423
  const preview = firstLine.length < output.length ? `${firstLine}...` : firstLine
433
424
  setMessages((prev) => [
@@ -608,7 +599,7 @@ export function App({
608
599
  {/* Agent status footer — shows running background agents */}
609
600
  <AgentFooter agents={Object.values(runningAgents)} gitBranch={gitBranch} tick={agentTick} />
610
601
 
611
- {/* Status line — Claude Code style: compact, action-oriented */}
602
+ {/* Status line — Claude Code style */}
612
603
  <Box marginTop={1} flexDirection="column">
613
604
  {goalText && (
614
605
  <Box>
@@ -619,8 +610,8 @@ export function App({
619
610
  <Text color={PERMISSION_COLORS[permissionMode]}>
620
611
  ⏵⏵ {PERMISSION_LABELS[permissionMode]} (shift+tab to cycle)
621
612
  </Text>
622
- <Text dimColor> · Esc to interrupt</Text>
623
- <Text dimColor> · ← agents</Text>
613
+ <Text dimColor> · esc to interrupt</Text>
614
+ <Text dimColor> · ← for agents</Text>
624
615
  </Box>
625
616
  </Box>
626
617
  </Box>
package/src/ui/chat.tsx CHANGED
@@ -93,8 +93,11 @@ const MessageRow = React.memo(
93
93
  )
94
94
 
95
95
  export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
96
- // In focus mode, compact tool activity into summary lines
97
- const displayMessages = focusMode ? compactForFocus(messages) : messages
96
+ // Group consecutive tool calls into compact summaries to reduce visual noise.
97
+ // In focus mode, use the more aggressive compactForFocus.
98
+ const displayMessages = focusMode
99
+ ? compactForFocus(messages)
100
+ : compactToolGroups(messages)
98
101
 
99
102
  return (
100
103
  <Box flexDirection="column" marginY={1} flexGrow={1}>
@@ -141,6 +144,56 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
141
144
  )
142
145
  }
143
146
 
147
+ /**
148
+ * Group consecutive tool calls into compact one-liners for the normal view.
149
+ * Single tools show normally; 2+ consecutive tools get folded:
150
+ * ⏺ Read · Glob · Bash (3 tools)
151
+ */
152
+ function compactToolGroups(messages: ChatMessage[]): ChatMessage[] {
153
+ const result: ChatMessage[] = []
154
+ let toolGroup: ChatMessage[] = []
155
+
156
+ const flush = () => {
157
+ if (toolGroup.length === 0) return
158
+ if (toolGroup.length === 1) {
159
+ result.push(toolGroup[0]!)
160
+ } else {
161
+ const names = toolGroup
162
+ .filter((t) => t.toolMeta?.name && !t.toolMeta.name.startsWith('tools ('))
163
+ .map((t) => t.toolMeta!.name)
164
+ const uniqueNames = [...new Set(names)]
165
+ result.push({
166
+ role: 'system',
167
+ content: `⏺ ${uniqueNames.join(' · ')} (${names.length} tools)`,
168
+ toolMeta: {
169
+ name: `tools (${names.length})`,
170
+ input: uniqueNames.join(', '),
171
+ collapsed: true,
172
+ },
173
+ })
174
+ }
175
+ toolGroup = []
176
+ }
177
+
178
+ for (const msg of messages) {
179
+ // Tool results (no name, has output) get folded into the preceding tool group
180
+ if (msg.toolMeta && !msg.toolMeta.name && msg.toolMeta.output) {
181
+ toolGroup.push(msg)
182
+ continue
183
+ }
184
+ // Tool calls with a name
185
+ if (msg.toolMeta?.name && !msg.toolMeta.name.startsWith('tools (')) {
186
+ toolGroup.push(msg)
187
+ continue
188
+ }
189
+ // Already-grouped or non-tool message
190
+ flush()
191
+ result.push(msg)
192
+ }
193
+ flush()
194
+ return result
195
+ }
196
+
144
197
  /**
145
198
  * Compact messages for focus view:
146
199
  * - Groups consecutive tool calls into [▶ N tools] ↳ name1 ↳ name2 summary lines