@miphamai/cli 0.32.1 → 0.32.3
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 +94 -7
- package/src/ui/app.tsx +50 -27
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,95 @@ 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':
|
|
52
|
+
case 'Read':
|
|
53
|
+
case 'Glob':
|
|
54
|
+
case 'Grep':
|
|
55
|
+
return 'white'
|
|
56
|
+
case 'Write':
|
|
57
|
+
case 'Edit':
|
|
58
|
+
case 'Update':
|
|
59
|
+
return 'red'
|
|
60
|
+
case 'Agent':
|
|
61
|
+
case 'Task':
|
|
62
|
+
case 'Skill':
|
|
63
|
+
case 'Workflow':
|
|
64
|
+
return 'magenta'
|
|
65
|
+
case 'WebSearch':
|
|
66
|
+
case 'WebFetch':
|
|
67
|
+
return 'blue'
|
|
68
|
+
default:
|
|
69
|
+
return 'white'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Animate dots for the active tool indicator: cycles 1→2→3→1 dots. */
|
|
74
|
+
function animatedDots(tick: number): string {
|
|
75
|
+
const n = (tick % 3) + 1
|
|
76
|
+
return '.'.repeat(n)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Gerund verbs for agent progress — rotated every 2 seconds for visual liveliness. */
|
|
80
|
+
const GERUNDS = [
|
|
81
|
+
'Pondering',
|
|
82
|
+
'Contemplating',
|
|
83
|
+
'Cerebrating',
|
|
84
|
+
'Ruminating',
|
|
85
|
+
'Deliberating',
|
|
86
|
+
'Cogitating',
|
|
87
|
+
'Perambulating',
|
|
88
|
+
'Computing',
|
|
89
|
+
'Processing',
|
|
90
|
+
'Analyzing',
|
|
91
|
+
'Synthesizing',
|
|
92
|
+
'Orchestrating',
|
|
93
|
+
'Forging',
|
|
94
|
+
'Illuminating',
|
|
95
|
+
'Manifesting',
|
|
96
|
+
'Transmogrifying',
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
/** Pick a gerund that changes every 2 seconds (driven by tick). */
|
|
100
|
+
function rotatingGerund(tick: number): string {
|
|
101
|
+
return GERUNDS[Math.floor(tick / 2) % GERUNDS.length]!
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function AgentFooter({ agents, tick, activeTool, agentProgress }: AgentFooterProps) {
|
|
37
105
|
const { t } = useI18n()
|
|
38
106
|
const hasAgents = agents.length > 0
|
|
39
|
-
const
|
|
107
|
+
const hasTool = !!activeTool
|
|
108
|
+
const hasProgress = !!agentProgress
|
|
40
109
|
|
|
41
|
-
if (!hasAgents && !
|
|
110
|
+
if (!hasAgents && !hasTool && !hasProgress) return null
|
|
42
111
|
|
|
43
112
|
return (
|
|
44
113
|
<Box flexDirection="column" marginTop={1}>
|
|
45
|
-
{/*
|
|
46
|
-
{
|
|
114
|
+
{/* Active foreground tool indicator — Claude Code style [Bash command...] */}
|
|
115
|
+
{hasTool && (
|
|
47
116
|
<Box>
|
|
117
|
+
<Text color={toolIndicatorColor(activeTool!.name)}>
|
|
118
|
+
{' '}[{activeTool!.name} {activeTool!.detail.slice(0, 60)}
|
|
119
|
+
{animatedDots(tick)}]
|
|
120
|
+
</Text>
|
|
121
|
+
</Box>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
{/* Inline agent progress — ✻ Gerund… (elapsed · ↓ tokens) in orange */}
|
|
125
|
+
{hasProgress && (
|
|
126
|
+
<Box>
|
|
127
|
+
<Text color="#FFA500">
|
|
128
|
+
{' '}✻ {rotatingGerund(tick)}…
|
|
129
|
+
</Text>
|
|
48
130
|
<Text dimColor>
|
|
49
|
-
{'
|
|
131
|
+
{' ('}
|
|
132
|
+
{formatElapsed(Math.floor((Date.now() - agentProgress!.startTime) / 1000))}
|
|
133
|
+
{agentProgress!.tokensUsed && agentProgress!.tokensUsed > 0
|
|
134
|
+
? ` · ↓ ${formatTokens(agentProgress!.tokensUsed)} tokens`
|
|
135
|
+
: ''}
|
|
136
|
+
{')'}
|
|
50
137
|
</Text>
|
|
51
138
|
</Box>
|
|
52
139
|
)}
|
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,24 @@ 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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// Not a git repo — leave empty
|
|
197
|
-
}
|
|
198
|
-
}, [])
|
|
199
|
-
|
|
200
|
-
// Tick timer for agent elapsed displays (re-renders every second while agents are running)
|
|
186
|
+
/** Active foreground tool indicator: [Bash command...], [Update file.ts...], etc. */
|
|
187
|
+
const [activeTool, setActiveTool] = useState<{ name: string; detail: string } | null>(null)
|
|
188
|
+
// Refs for immediate state (bypasses React batching so footer renders between rapid chunks)
|
|
189
|
+
const activeToolRef = useRef<{ name: string; detail: string } | null>(null)
|
|
190
|
+
const agentProgressRef = useRef<AgentProgress | null>(null)
|
|
191
|
+
|
|
192
|
+
// Tick timer for agent elapsed displays (re-renders every second while agents are running).
|
|
193
|
+
// Uses refs so the timer doesn't stop between state batches.
|
|
201
194
|
useEffect(() => {
|
|
202
195
|
const hasRunning =
|
|
203
|
-
Object.values(runningAgents).some((a) => a.status === 'running') ||
|
|
196
|
+
Object.values(runningAgents).some((a) => a.status === 'running') ||
|
|
197
|
+
agentProgressRef.current !== null ||
|
|
198
|
+
activeToolRef.current !== null
|
|
204
199
|
if (!hasRunning) return
|
|
205
200
|
const i = setInterval(() => setAgentTick((t) => t + 1), 1000)
|
|
206
201
|
return () => clearInterval(i)
|
|
207
|
-
}, [runningAgents, agentProgress])
|
|
202
|
+
}, [runningAgents, agentProgress, activeTool])
|
|
208
203
|
|
|
209
204
|
// Sync running agents from BackgroundAgentRegistry into React state.
|
|
210
205
|
// Called after Agent tool results and task notifications to keep the footer current.
|
|
@@ -447,6 +442,13 @@ export function App({
|
|
|
447
442
|
setMessages((prev) => [...prev, { role: 'user', content: input }])
|
|
448
443
|
setIsLoading(true)
|
|
449
444
|
|
|
445
|
+
// Start agent progress indicator immediately for ALL processing
|
|
446
|
+
const progressStart = Date.now()
|
|
447
|
+
const progress: AgentProgress = { name: '', description: '', startTime: progressStart, tokensUsed: 0 }
|
|
448
|
+
agentProgressRef.current = progress
|
|
449
|
+
setAgentProgress(progress)
|
|
450
|
+
setAgentTick((t) => t + 1)
|
|
451
|
+
|
|
450
452
|
const controller = new AbortController()
|
|
451
453
|
abortRef.current = controller
|
|
452
454
|
|
|
@@ -520,15 +522,24 @@ export function App({
|
|
|
520
522
|
const isAgent = toolName === 'Agent' || toolName === 'Task'
|
|
521
523
|
const detail = formatToolDetail(toolName, chunk.toolUse.input)
|
|
522
524
|
|
|
525
|
+
// Show [ToolName detail...] activity indicator for ALL tools (via ref for immediate render)
|
|
526
|
+
const toolEntry = { name: toolDisplayName(toolName), detail }
|
|
527
|
+
activeToolRef.current = toolEntry
|
|
528
|
+
setActiveTool(toolEntry)
|
|
529
|
+
setAgentTick((t) => t + 1)
|
|
530
|
+
|
|
523
531
|
if (isAgent) {
|
|
524
|
-
|
|
532
|
+
const ap: AgentProgress = {
|
|
525
533
|
name: detail || (chunk.toolUse.input.subagent_type as string) || 'General-purpose',
|
|
526
534
|
description:
|
|
527
535
|
(chunk.toolUse.input.description as string) ||
|
|
528
536
|
(chunk.toolUse.input.prompt as string) ||
|
|
529
537
|
'',
|
|
530
538
|
startTime: Date.now(),
|
|
531
|
-
}
|
|
539
|
+
}
|
|
540
|
+
agentProgressRef.current = ap
|
|
541
|
+
setAgentProgress(ap)
|
|
542
|
+
setAgentTick((t) => t + 1)
|
|
532
543
|
}
|
|
533
544
|
|
|
534
545
|
// Show tool call as a visible, collapsed message
|
|
@@ -552,7 +563,10 @@ export function App({
|
|
|
552
563
|
}
|
|
553
564
|
|
|
554
565
|
if (chunk.type === 'tool_result') {
|
|
555
|
-
|
|
566
|
+
// Clear tool indicator but keep agent progress (AI is still processing)
|
|
567
|
+
activeToolRef.current = null
|
|
568
|
+
setActiveTool(null)
|
|
569
|
+
setAgentTick((t) => t + 1)
|
|
556
570
|
// Sync background agents (e.g. Agent tool may have spawned them)
|
|
557
571
|
syncBgAgents()
|
|
558
572
|
const output = chunk.content ? String(chunk.content).trim() : ''
|
|
@@ -589,12 +603,14 @@ export function App({
|
|
|
589
603
|
}
|
|
590
604
|
}
|
|
591
605
|
|
|
592
|
-
if (chunk.type === 'usage'
|
|
606
|
+
if (chunk.type === 'usage') {
|
|
593
607
|
const totalTokens = (chunk.inputTokens || 0) + (chunk.outputTokens || 0)
|
|
594
|
-
if (totalTokens > 0) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
608
|
+
if (totalTokens > 0 && agentProgressRef.current) {
|
|
609
|
+
agentProgressRef.current = {
|
|
610
|
+
...agentProgressRef.current,
|
|
611
|
+
tokensUsed: (agentProgressRef.current.tokensUsed || 0) + totalTokens,
|
|
612
|
+
}
|
|
613
|
+
setAgentProgress({ ...agentProgressRef.current })
|
|
598
614
|
}
|
|
599
615
|
}
|
|
600
616
|
|
|
@@ -638,6 +654,12 @@ export function App({
|
|
|
638
654
|
flushStreamBuffer()
|
|
639
655
|
setIsLoading(false)
|
|
640
656
|
abortRef.current = null
|
|
657
|
+
// Clear all progress/tool indicators
|
|
658
|
+
agentProgressRef.current = null
|
|
659
|
+
setAgentProgress(null)
|
|
660
|
+
activeToolRef.current = null
|
|
661
|
+
setActiveTool(null)
|
|
662
|
+
setAgentTick((t) => t + 1)
|
|
641
663
|
// Auto-save checkpoint after each AI response
|
|
642
664
|
if (assistantContent) {
|
|
643
665
|
engine.getContext().saveCheckpoint('post-turn')
|
|
@@ -834,11 +856,12 @@ export function App({
|
|
|
834
856
|
</Box>
|
|
835
857
|
)}
|
|
836
858
|
|
|
837
|
-
{/* Agent status footer — shows running background agents */}
|
|
859
|
+
{/* Agent status footer — shows active tool indicator + inline agent progress + running background agents */}
|
|
838
860
|
<AgentFooter
|
|
839
861
|
agents={Object.values(runningAgents)}
|
|
840
|
-
gitBranch={gitBranch}
|
|
841
862
|
tick={agentTick}
|
|
863
|
+
activeTool={activeTool}
|
|
864
|
+
agentProgress={agentProgress}
|
|
842
865
|
/>
|
|
843
866
|
|
|
844
867
|
{/* Status line — Claude Code style */}
|