Skip to content

Commit 97b766a

Browse files
committed
feat(workflow): add agent character configuration and activity-based display enhancements
- Integrated agent character configuration system with customizable ASCII expressions and phrases. - Updated output components to dynamically display faces and phrases based on the agent's activity. - Enhanced `useLogStream` to detect and update current activity from log lines. - Added engine specification to workflow steps in templates for improved clarity.
1 parent a319380 commit 97b766a

7 files changed

Lines changed: 326 additions & 3 deletions

File tree

config/agent-characters.json

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"engines": {
3+
"claude": {
4+
"name": "Claude",
5+
"baseFace": "(˶ᵔ ᵕ ᵔ˶)",
6+
"expressions": {
7+
"thinking": "(╭ರ_•́)",
8+
"tool": "(•̀ᴗ•́)و",
9+
"error": "(╥﹏╥)",
10+
"idle": "(˶ᵔ ᵕ ᵔ˶)"
11+
},
12+
"phrases": {
13+
"thinking": ["Hmm, let me think...", "Processing..."],
14+
"tool": ["On it!", "Working..."],
15+
"error": ["Oops, something went wrong", "Let me try again"],
16+
"idle": ["Ready when you are", "Waiting..."]
17+
}
18+
},
19+
"codex": {
20+
"name": "Codex",
21+
"baseFace": "[•_•]",
22+
"expressions": {
23+
"thinking": "[•_•]~",
24+
"tool": "[◉_◉]",
25+
"error": "[x_x]",
26+
"idle": "[•_•]"
27+
},
28+
"phrases": {
29+
"thinking": ["Analyzing...", "Computing..."],
30+
"tool": ["Executing...", "Running..."],
31+
"error": ["Error encountered", "Retrying..."],
32+
"idle": ["Standing by", "Awaiting input"]
33+
}
34+
},
35+
"cursor": {
36+
"name": "Cursor",
37+
"baseFace": "⌨_⌨",
38+
"expressions": {
39+
"thinking": "⌨~⌨",
40+
"tool": "⌨!⌨",
41+
"error": "⌨x⌨",
42+
"idle": "⌨_⌨"
43+
},
44+
"phrases": {
45+
"thinking": ["Thinking...", "Processing..."],
46+
"tool": ["Executing...", "Working..."],
47+
"error": ["Error", "Something went wrong"],
48+
"idle": ["Ready", "Waiting..."]
49+
}
50+
},
51+
"auggie": {
52+
"name": "Auggie",
53+
"baseFace": "◕‿◕",
54+
"expressions": {
55+
"thinking": "◕~◕",
56+
"tool": "◕!◕",
57+
"error": "◕_◕",
58+
"idle": "◕‿◕"
59+
},
60+
"phrases": {
61+
"thinking": ["Pondering...", "Considering..."],
62+
"tool": ["Let me do that!", "Working on it..."],
63+
"error": ["Hmm, that didn't work", "Let me try again"],
64+
"idle": ["Here to help!", "Ready for action"]
65+
}
66+
},
67+
"mistral": {
68+
"name": "Mistral",
69+
"baseFace": "≋_≋",
70+
"expressions": {
71+
"thinking": "≋~≋",
72+
"tool": "≋!≋",
73+
"error": "≋x≋",
74+
"idle": "≋_≋"
75+
},
76+
"phrases": {
77+
"thinking": ["Analyzing...", "Processing..."],
78+
"tool": ["Running...", "Executing..."],
79+
"error": ["Error detected", "Retrying..."],
80+
"idle": ["Awaiting", "Standing by"]
81+
}
82+
},
83+
"opencode": {
84+
"name": "OpenCode",
85+
"baseFace": "{•_•}",
86+
"expressions": {
87+
"thinking": "{•~•}",
88+
"tool": "{•!•}",
89+
"error": "{•x•}",
90+
"idle": "{•_•}"
91+
},
92+
"phrases": {
93+
"thinking": ["Processing...", "Analyzing..."],
94+
"tool": ["Executing...", "Working..."],
95+
"error": ["Error", "Retrying..."],
96+
"idle": ["Ready", "Waiting..."]
97+
}
98+
},
99+
"ccr": {
100+
"name": "CCR",
101+
"baseFace": "<•_•>",
102+
"expressions": {
103+
"thinking": "<•~•>",
104+
"tool": "<•!•>",
105+
"error": "<•x•>",
106+
"idle": "<•_•>"
107+
},
108+
"phrases": {
109+
"thinking": ["Thinking...", "Processing..."],
110+
"tool": ["Running...", "Working..."],
111+
"error": ["Error", "Retrying..."],
112+
"idle": ["Ready", "Waiting..."]
113+
}
114+
}
115+
},
116+
"defaults": {
117+
"name": "Agent",
118+
"baseFace": "(•_•)",
119+
"expressions": {
120+
"thinking": "(•_•)?",
121+
"tool": "(•_•)!",
122+
"error": "(•_•);;",
123+
"idle": "(•_•)"
124+
},
125+
"phrases": {
126+
"thinking": ["Thinking..."],
127+
"tool": ["Working..."],
128+
"error": ["Error"],
129+
"idle": ["Ready"]
130+
}
131+
}
132+
}

src/cli/tui/routes/workflow/components/output/output-window.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { LogTable } from "../shared/log-table"
1818
import { groupLinesWithTables } from "../shared/markdown-table"
1919
import { PromptLine, type PromptLineState } from "./prompt-line"
2020
import type { AgentState, SubAgentState, InputState, ControllerState } from "../../state/types"
21+
import { getFace, getPhrase } from "../../../../shared/config/agent-characters.js"
22+
import type { ActivityType } from "../../../../shared/config/agent-characters.types.js"
2123

2224
// Rotating messages shown while connecting to agent
2325
const CONNECTING_MESSAGES = [
@@ -38,6 +40,7 @@ export interface OutputWindowProps {
3840
maxLines?: number
3941
connectingMessageIndex?: number
4042
latestThinking?: string | null
43+
currentActivity?: ActivityType | null
4144
// Prompt line props
4245
inputState?: InputState | null
4346
workflowStatus?: string
@@ -168,6 +171,10 @@ export function OutputWindow(props: OutputWindowProps) {
168171
const displayEngine = () => isControllerActive() ? props.controllerState!.engine : props.currentAgent?.engine
169172
const displayModel = () => isControllerActive() ? props.controllerState!.model : props.currentAgent?.model
170173

174+
// Get character face and phrase based on engine and current activity
175+
const currentFace = () => getFace(displayEngine() ?? "default", props.currentActivity ?? "idle")
176+
const currentPhrase = () => getPhrase(displayEngine() ?? "default", props.currentActivity ?? "idle")
177+
171178
// Check if we have something to display (agent or controller in controller view)
172179
const hasDisplayContent = () => props.currentAgent != null || isControllerViewMode()
173180

@@ -260,7 +267,7 @@ export function OutputWindow(props: OutputWindowProps) {
260267
<box flexDirection="row" justifyContent="space-between" paddingRight={2}>
261268
<box flexDirection="row">
262269
<text fg={themeCtx.theme.border}></text>
263-
<text fg={themeCtx.theme.text}>{isRunning() && props.latestThinking ? "(╭ರ_•́)" : "(˶ᵔ ᵕ ᵔ˶)"}</text>
270+
<text fg={themeCtx.theme.text}>{currentFace()}</text>
264271
<text> </text>
265272
<text fg={themeCtx.theme.text} attributes={1}>{displayName()}</text>
266273
</box>
@@ -278,7 +285,7 @@ export function OutputWindow(props: OutputWindowProps) {
278285
<Show when={!isWideLayout()}>
279286
<box flexDirection="row">
280287
<text fg={themeCtx.theme.border}></text>
281-
<text fg={themeCtx.theme.text}>{isRunning() && props.latestThinking ? "(╭ರ_•́)" : "(˶ᵔ ᵕ ᵔ˶)"}</text>
288+
<text fg={themeCtx.theme.text}>{currentFace()}</text>
282289
<text> </text>
283290
<text fg={themeCtx.theme.text} attributes={1}>{displayName()}</text>
284291
</box>
@@ -294,7 +301,7 @@ export function OutputWindow(props: OutputWindowProps) {
294301

295302
<box flexDirection="row">
296303
<text fg={themeCtx.theme.border}></text>
297-
<Show when={isRunning() && props.latestThinking} fallback={<text fg={themeCtx.theme.textMuted}>Waiting...</text>}>
304+
<Show when={isRunning() && props.latestThinking} fallback={<text fg={themeCtx.theme.textMuted}>{currentPhrase()}</text>}>
298305
<TypingText text={`↳ ${props.latestThinking}`} speed={30} />
299306
</Show>
300307
</box>

src/cli/tui/routes/workflow/components/shells/controller-shell.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function ControllerShell(props: ControllerShellProps) {
2828
isConnecting={shell.logStream.isConnecting}
2929
error={shell.logStream.error}
3030
latestThinking={shell.logStream.latestThinking}
31+
currentActivity={shell.logStream.currentActivity}
3132
hasMoreAbove={shell.logStream.hasMoreAbove}
3233
isLoadingEarlier={shell.logStream.isLoadingEarlier}
3334
loadEarlierError={shell.logStream.loadEarlierError}

src/cli/tui/routes/workflow/components/shells/executing-shell.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export function ExecutingShell(props: ExecutingShellProps) {
6161
isConnecting={shell.logStream.isConnecting}
6262
error={shell.logStream.error}
6363
latestThinking={shell.logStream.latestThinking}
64+
currentActivity={shell.logStream.currentActivity}
6465
hasMoreAbove={shell.logStream.hasMoreAbove}
6566
isLoadingEarlier={shell.logStream.isLoadingEarlier}
6667
loadEarlierError={shell.logStream.loadEarlierError}

src/cli/tui/routes/workflow/hooks/useLogStream.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { AgentMonitorService } from "../../../../../agents/monitoring/monitor.js
1313
import type { AgentRecord } from "../../../../../agents/monitoring/types.js"
1414
import { existsSync, statSync, openSync, readSync, closeSync, readFileSync } from "fs"
1515
import type { AgentStatus } from "../state/types.js"
16+
import { processOutputChunk } from "../state/output.js"
17+
import type { ActivityType } from "../../../shared/config/agent-characters.types.js"
1618

1719
// Debug logging (writes to tui-debug.log when DEBUG is enabled)
1820
const DEBUG_ENABLED = process.env.DEBUG &&
@@ -51,6 +53,7 @@ export interface LogStreamResult {
5153
agentName: string
5254
isRunning: boolean
5355
latestThinking: string | null
56+
currentActivity: ActivityType | null
5457
hasMoreAbove: boolean
5558
isLoadingEarlier: boolean
5659
loadEarlierError: string | null
@@ -331,6 +334,7 @@ export function useLogStream(
331334
const [isLoadingEarlier, setIsLoadingEarlier] = createSignal(false)
332335
const [loadEarlierError, setLoadEarlierError] = createSignal<string | null>(null)
333336
const [pauseTrimming, setPauseTrimming] = createSignal(false)
337+
const [currentActivity, setCurrentActivity] = createSignal<ActivityType | null>(null)
334338

335339
/**
336340
* Load earlier lines when user scrolls to top of windowed view
@@ -454,6 +458,24 @@ export function useLogStream(
454458
return null
455459
}
456460

461+
/**
462+
* Extract current activity from the most recent log lines
463+
* Looks at the last few lines to determine what the agent is doing
464+
*/
465+
function extractCurrentActivity(fileLines: string[]): ActivityType {
466+
// Check the last 5 lines to determine activity
467+
const recentLines = fileLines.slice(-5)
468+
for (let i = recentLines.length - 1; i >= 0; i--) {
469+
const line = recentLines[i]
470+
if (!line) continue
471+
const chunk = processOutputChunk(line)
472+
if (chunk.type === "thinking") return "thinking"
473+
if (chunk.type === "tool") return "tool"
474+
if (chunk.type === "error") return "error"
475+
}
476+
return "idle"
477+
}
478+
457479
/**
458480
* Update log lines from file using incremental reading
459481
*/
@@ -488,6 +510,7 @@ export function useLogStream(
488510
startOffset: trimCount
489511
})
490512
setLatestThinking(thinking)
513+
setCurrentActivity(extractCurrentActivity(fileLines))
491514
setFileSize(currentFileSize)
492515
setIsConnecting(false)
493516
setError(null)
@@ -509,6 +532,7 @@ export function useLogStream(
509532
startOffset: trimCount
510533
})
511534
setLatestThinking(thinking)
535+
setCurrentActivity(extractCurrentActivity(result.newLines))
512536
setFileSize(result.fileSize)
513537
logDebug('updateLogs: file reset, lines=%d', filteredLines.length)
514538
return true
@@ -536,6 +560,9 @@ export function useLogStream(
536560
if (thinking) {
537561
setLatestThinking(thinking)
538562
}
563+
564+
// Update activity based on new lines
565+
setCurrentActivity(extractCurrentActivity(filteredNewLines))
539566
}
540567

541568
setFileSize(result.fileSize)
@@ -760,6 +787,9 @@ export function useLogStream(
760787
get latestThinking() {
761788
return latestThinking()
762789
},
790+
get currentActivity() {
791+
return currentActivity()
792+
},
763793
get isLoadingEarlier() {
764794
return isLoadingEarlier()
765795
},

0 commit comments

Comments
 (0)