@miphamai/cli 0.32.0 → 0.32.2
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/ui/agent-footer.tsx +67 -7
- package/src/ui/app.tsx +11 -17
- package/src/ui/chat.tsx +39 -33
package/package.json
CHANGED
package/src/ui/agent-footer.tsx
CHANGED
|
@@ -11,11 +11,23 @@ export interface AgentEntry {
|
|
|
11
11
|
status: 'running' | 'completed'
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface AgentProgress {
|
|
15
|
+
name: string
|
|
16
|
+
description: string
|
|
17
|
+
startTime: number
|
|
18
|
+
tokensUsed?: number
|
|
19
|
+
isTask?: boolean
|
|
20
|
+
id?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
interface AgentFooterProps {
|
|
15
24
|
agents: AgentEntry[]
|
|
16
|
-
gitBranch: string
|
|
17
25
|
/** Tick counter for live elapsed-time re-renders. */
|
|
18
26
|
tick: number
|
|
27
|
+
/** Active foreground tool: shown as [ToolName detail...] before agent lines. */
|
|
28
|
+
activeTool?: { name: string; detail: string } | null
|
|
29
|
+
/** Inline agent progress from tool_use streaming: ✻ Gerund… (elapsed · tokens). */
|
|
30
|
+
agentProgress?: AgentProgress | null
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
/** Format elapsed seconds into human-readable string (e.g. "2m 51s", "38s"). */
|
|
@@ -33,20 +45,68 @@ function formatTokens(n: number): string {
|
|
|
33
45
|
return String(n)
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
/** Map tool name to Ink color for the [Tool ...] indicator. */
|
|
49
|
+
function toolIndicatorColor(name: string): string {
|
|
50
|
+
switch (name) {
|
|
51
|
+
case 'Bash': case 'Read': case 'Glob': case 'Grep': return 'white'
|
|
52
|
+
case 'Write': case 'Edit': case 'Update': return 'red'
|
|
53
|
+
case 'Agent': case 'Task': case 'Skill': case 'Workflow': return 'magenta'
|
|
54
|
+
case 'WebSearch': case 'WebFetch': return 'blue'
|
|
55
|
+
default: return 'white'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Animate dots for the active tool indicator: cycles 1→2→3→1 dots. */
|
|
60
|
+
function animatedDots(tick: number): string {
|
|
61
|
+
const n = (tick % 3) + 1
|
|
62
|
+
return '.'.repeat(n)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Gerund verbs for agent progress — rotated every 2 seconds for visual liveliness. */
|
|
66
|
+
const GERUNDS = [
|
|
67
|
+
'Pondering', 'Contemplating', 'Cerebrating', 'Ruminating',
|
|
68
|
+
'Deliberating', 'Cogitating', 'Perambulating', 'Computing',
|
|
69
|
+
'Processing', 'Analyzing', 'Synthesizing', 'Orchestrating',
|
|
70
|
+
'Forging', 'Illuminating', 'Manifesting', 'Transmogrifying',
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
/** Pick a gerund that changes every 2 seconds (driven by tick). */
|
|
74
|
+
function rotatingGerund(tick: number): string {
|
|
75
|
+
return GERUNDS[Math.floor(tick / 2) % GERUNDS.length]!
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function AgentFooter({ agents, tick, activeTool, agentProgress }: AgentFooterProps) {
|
|
37
79
|
const { t } = useI18n()
|
|
38
80
|
const hasAgents = agents.length > 0
|
|
39
|
-
const
|
|
81
|
+
const hasTool = !!activeTool
|
|
82
|
+
const hasProgress = !!agentProgress
|
|
40
83
|
|
|
41
|
-
if (!hasAgents && !
|
|
84
|
+
if (!hasAgents && !hasTool && !hasProgress) return null
|
|
42
85
|
|
|
43
86
|
return (
|
|
44
87
|
<Box flexDirection="column" marginTop={1}>
|
|
45
|
-
{/*
|
|
46
|
-
{
|
|
88
|
+
{/* Active foreground tool indicator — Claude Code style [Bash command...] */}
|
|
89
|
+
{hasTool && (
|
|
47
90
|
<Box>
|
|
91
|
+
<Text color={toolIndicatorColor(activeTool!.name)}>
|
|
92
|
+
{' '}[{activeTool!.name} {activeTool!.detail.slice(0, 60)}{animatedDots(tick)}]
|
|
93
|
+
</Text>
|
|
94
|
+
</Box>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* Inline agent progress — ✻ Gerund… (elapsed · ↓ tokens) in orange */}
|
|
98
|
+
{hasProgress && (
|
|
99
|
+
<Box>
|
|
100
|
+
<Text color="#FFA500">
|
|
101
|
+
{' '}✻ {rotatingGerund(tick)}…
|
|
102
|
+
</Text>
|
|
48
103
|
<Text dimColor>
|
|
49
|
-
{'
|
|
104
|
+
{' ('}
|
|
105
|
+
{formatElapsed(Math.floor((Date.now() - agentProgress!.startTime) / 1000))}
|
|
106
|
+
{agentProgress!.tokensUsed && agentProgress!.tokensUsed > 0
|
|
107
|
+
? ` · ↓ ${formatTokens(agentProgress!.tokensUsed)} tokens`
|
|
108
|
+
: ''}
|
|
109
|
+
{')'}
|
|
50
110
|
</Text>
|
|
51
111
|
</Box>
|
|
52
112
|
)}
|
package/src/ui/app.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useState, useCallback, useEffect, useRef, useMemo } from 'react'
|
|
2
2
|
import { Box, Text, useInput } from 'ink'
|
|
3
3
|
import TextInput from 'ink-text-input'
|
|
4
|
-
import { readFileSync } from 'node:fs'
|
|
5
4
|
import type { QueryEngine } from '../core/engine'
|
|
6
5
|
import type { RemoteEngine } from '../daemon/remote-engine'
|
|
7
6
|
import type { MiphamConfig } from '../shared/index.ts'
|
|
@@ -183,28 +182,18 @@ export function App({
|
|
|
183
182
|
const [agentProgress, setAgentProgress] = useState<AgentProgress | null>(null)
|
|
184
183
|
// Multi-agent tracking: keyed by agent/task ID, shows all running + recently completed agents
|
|
185
184
|
const [runningAgents, setRunningAgents] = useState<Record<string, AgentEntry>>({})
|
|
186
|
-
const [gitBranch, setGitBranch] = useState('')
|
|
187
185
|
const [agentTick, setAgentTick] = useState(0)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
try {
|
|
192
|
-
const head = readFileSync('.git/HEAD', 'utf8').trim()
|
|
193
|
-
const m = head.match(/^ref: refs\/heads\/(.+)$/)
|
|
194
|
-
setGitBranch(m ? m[1]! : head.slice(0, 7))
|
|
195
|
-
} catch {
|
|
196
|
-
// Not a git repo — leave empty
|
|
197
|
-
}
|
|
198
|
-
}, [])
|
|
186
|
+
/** Active foreground tool indicator: [Bash command...], [Update file.ts...], etc. */
|
|
187
|
+
const [activeTool, setActiveTool] = useState<{ name: string; detail: string } | null>(null)
|
|
199
188
|
|
|
200
189
|
// Tick timer for agent elapsed displays (re-renders every second while agents are running)
|
|
201
190
|
useEffect(() => {
|
|
202
191
|
const hasRunning =
|
|
203
|
-
Object.values(runningAgents).some((a) => a.status === 'running') || agentProgress !== null
|
|
192
|
+
Object.values(runningAgents).some((a) => a.status === 'running') || agentProgress !== null || activeTool !== null
|
|
204
193
|
if (!hasRunning) return
|
|
205
194
|
const i = setInterval(() => setAgentTick((t) => t + 1), 1000)
|
|
206
195
|
return () => clearInterval(i)
|
|
207
|
-
}, [runningAgents, agentProgress])
|
|
196
|
+
}, [runningAgents, agentProgress, activeTool])
|
|
208
197
|
|
|
209
198
|
// Sync running agents from BackgroundAgentRegistry into React state.
|
|
210
199
|
// Called after Agent tool results and task notifications to keep the footer current.
|
|
@@ -520,6 +509,9 @@ export function App({
|
|
|
520
509
|
const isAgent = toolName === 'Agent' || toolName === 'Task'
|
|
521
510
|
const detail = formatToolDetail(toolName, chunk.toolUse.input)
|
|
522
511
|
|
|
512
|
+
// Show [ToolName detail...] activity indicator for ALL tools
|
|
513
|
+
setActiveTool({ name: toolDisplayName(toolName), detail })
|
|
514
|
+
|
|
523
515
|
if (isAgent) {
|
|
524
516
|
setAgentProgress({
|
|
525
517
|
name: detail || (chunk.toolUse.input.subagent_type as string) || 'General-purpose',
|
|
@@ -553,6 +545,7 @@ export function App({
|
|
|
553
545
|
|
|
554
546
|
if (chunk.type === 'tool_result') {
|
|
555
547
|
setAgentProgress(null)
|
|
548
|
+
setActiveTool(null)
|
|
556
549
|
// Sync background agents (e.g. Agent tool may have spawned them)
|
|
557
550
|
syncBgAgents()
|
|
558
551
|
const output = chunk.content ? String(chunk.content).trim() : ''
|
|
@@ -834,11 +827,12 @@ export function App({
|
|
|
834
827
|
</Box>
|
|
835
828
|
)}
|
|
836
829
|
|
|
837
|
-
{/* Agent status footer — shows running background agents */}
|
|
830
|
+
{/* Agent status footer — shows active tool indicator + inline agent progress + running background agents */}
|
|
838
831
|
<AgentFooter
|
|
839
832
|
agents={Object.values(runningAgents)}
|
|
840
|
-
gitBranch={gitBranch}
|
|
841
833
|
tick={agentTick}
|
|
834
|
+
activeTool={activeTool}
|
|
835
|
+
agentProgress={agentProgress}
|
|
842
836
|
/>
|
|
843
837
|
|
|
844
838
|
{/* Status line — Claude Code style */}
|
package/src/ui/chat.tsx
CHANGED
|
@@ -157,52 +157,58 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
160
|
+
* Pair each tool_use message with its following tool_result message(s),
|
|
161
|
+
* showing every tool individually with its name, detail, and output.
|
|
162
|
+
*
|
|
163
|
+
* Claude Code parity: each tool renders as:
|
|
164
|
+
* ⏺ ToolName (detail) ← colored per tool type
|
|
165
|
+
* ⎿ output summary ← dimmed below
|
|
166
|
+
*
|
|
167
|
+
* Only true duplicates (same tool name + same detail) are collapsed
|
|
168
|
+
* into a single entry with a count.
|
|
163
169
|
*/
|
|
164
170
|
function compactToolGroups(messages: ChatMessage[]): ChatMessage[] {
|
|
165
171
|
const result: ChatMessage[] = []
|
|
166
|
-
let
|
|
172
|
+
let current: ChatMessage | null = null
|
|
167
173
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
result.push(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.filter((t) => t.toolMeta?.name && !t.toolMeta.name.startsWith('tools ('))
|
|
175
|
-
.map((t) => t.toolMeta!.name)
|
|
176
|
-
const uniqueNames = [...new Set(names)]
|
|
177
|
-
result.push({
|
|
178
|
-
role: 'system',
|
|
179
|
-
content: `⏺ ${uniqueNames.join(' · ')} (${names.length} tools)`,
|
|
180
|
-
toolMeta: {
|
|
181
|
-
name: `tools (${names.length})`,
|
|
182
|
-
input: uniqueNames.join(', '),
|
|
183
|
-
collapsed: true,
|
|
184
|
-
},
|
|
185
|
-
})
|
|
174
|
+
for (const msg of messages) {
|
|
175
|
+
// Tool call with a name → flush previous, start a new one
|
|
176
|
+
if (msg.toolMeta?.name && !msg.toolMeta.name.startsWith('tools (')) {
|
|
177
|
+
if (current) result.push(current)
|
|
178
|
+
current = { ...msg }
|
|
179
|
+
continue
|
|
186
180
|
}
|
|
187
|
-
toolGroup = []
|
|
188
|
-
}
|
|
189
181
|
|
|
190
|
-
|
|
191
|
-
// Tool results (no name, has output) get folded into the preceding tool group
|
|
182
|
+
// Tool result (no name, has output) → merge output into current tool
|
|
192
183
|
if (msg.toolMeta && !msg.toolMeta.name && msg.toolMeta.output) {
|
|
193
|
-
|
|
184
|
+
if (current) {
|
|
185
|
+
current = {
|
|
186
|
+
...current,
|
|
187
|
+
content: msg.content,
|
|
188
|
+
toolMeta: {
|
|
189
|
+
...current.toolMeta!,
|
|
190
|
+
output: msg.toolMeta.output,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Orphan tool result (no preceding tool_use) → skip
|
|
194
195
|
continue
|
|
195
196
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
|
|
198
|
+
// Already-grouped message (from a previous compact cycle) or non-tool
|
|
199
|
+
if (current) {
|
|
200
|
+
result.push(current)
|
|
201
|
+
current = null
|
|
202
|
+
}
|
|
203
|
+
// Skip orphan tool-result messages without a preceding tool_use
|
|
204
|
+
if (msg.toolMeta && !msg.toolMeta.name && msg.toolMeta.output) {
|
|
199
205
|
continue
|
|
200
206
|
}
|
|
201
|
-
// Already-grouped or non-tool message
|
|
202
|
-
flush()
|
|
203
207
|
result.push(msg)
|
|
204
208
|
}
|
|
205
|
-
|
|
209
|
+
|
|
210
|
+
// Flush any remaining tool
|
|
211
|
+
if (current) result.push(current)
|
|
206
212
|
return result
|
|
207
213
|
}
|
|
208
214
|
|