Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extension/src/codex-rollout-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ export class CodexRolloutParser {
agent: ORCHESTRATOR_NAME,
role,
content: text.slice(0, MESSAGE_MAX),
runtime: 'codex',
},
})
this.emitContextUpdate(state)
Expand Down Expand Up @@ -575,6 +576,7 @@ export class CodexRolloutParser {
agent: ORCHESTRATOR_NAME,
role: 'thinking',
content: text.slice(0, MESSAGE_MAX),
runtime: 'codex',
},
})
this.emitContextUpdate(state)
Expand Down
9 changes: 9 additions & 0 deletions extension/test/codex-rollout-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ describe('CodexRolloutParser', () => {
assert.equal(thinking[0].payload.content, '**Planning directory listing**')
})

it('tags Codex transcript messages with the codex runtime', () => {
const { events } = runFixture()
const messages = events.filter(e => e.type === 'message')
assert.ok(messages.length > 0)
for (const event of messages) {
assert.equal(event.payload.runtime, 'codex')
}
})

it('pairs function_call with function_call_output by call_id', () => {
const { events, state } = runFixture()
const starts = events.filter(e => e.type === 'tool_call_start' && e.payload.tool === 'exec_command')
Expand Down
3 changes: 2 additions & 1 deletion web/components/agent-visualizer/canvas/draw-bubbles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Agent, NODE } from '@/lib/agent-types'
import { getMessageSenderLabel } from '@/lib/agent-runtime'
import { COLORS, withAlpha } from '@/lib/colors'
import { BUBBLE_MAX_W, BUBBLE_GAP, BUBBLE_MAX_LINES, AGENT_DRAW, BUBBLE_DRAW } from '@/lib/canvas-constants'
import { bubbleAlpha } from './bubble-utils'
Expand Down Expand Up @@ -29,7 +30,7 @@ export function drawMessageBubblesWorld(
const isThinking = role === 'thinking'
const bgColor = isThinking ? COLORS.bubbleThinkingBase : role === 'user' ? COLORS.bubbleUserBase : COLORS.bubbleAssistantBase
const textColor = isThinking ? COLORS.roleThinkingText : role === 'user' ? COLORS.roleUserText : COLORS.roleAssistantText
const label = isThinking ? '\uD83D\uDCAD THINKING' : role === 'user' ? 'USER' : 'CLAUDE'
const label = isThinking ? `\uD83D\uDCAD ${getMessageSenderLabel(role, agent.runtime)}` : getMessageSenderLabel(role, agent.runtime)

// Thinking bubbles: smaller font, tighter spacing, more translucent
const style = isThinking ? BUBBLE_DRAW.thinking : BUBBLE_DRAW.normal
Expand Down
6 changes: 4 additions & 2 deletions web/components/agent-visualizer/chat-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { CARD, Z, type AgentState } from '@/lib/agent-types'
import { CARD, Z, type Agent, type AgentState } from '@/lib/agent-types'
import { COLORS, getStateColor } from '@/lib/colors'
import { TranscriptMessage } from './transcript-message'
import type { ConversationMessage } from '@/hooks/simulation/types'
Expand All @@ -11,6 +11,7 @@ interface ChatPanelProps {
visible: boolean
agentName: string
agentState: AgentState
agentRuntime?: Agent['runtime']
conversation: ConversationMessage[]
onClose: () => void
}
Expand All @@ -19,6 +20,7 @@ export function AgentChatPanel({
visible,
agentName,
agentState,
agentRuntime,
conversation,
onClose,
}: ChatPanelProps) {
Expand Down Expand Up @@ -62,7 +64,7 @@ export function AgentChatPanel({
</div>
) : (
conversation.map((msg) => (
<TranscriptMessage key={msg.id} message={msg} />
<TranscriptMessage key={msg.id} message={msg} agentRuntime={agentRuntime} />
))
)}
</div>
Expand Down
8 changes: 6 additions & 2 deletions web/components/agent-visualizer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,12 @@ export function AgentVisualizer() {
// Only compute when the transcript panel is visible to avoid O(n log n) sort every frame
const sessionConversation = useMemo(() => {
if (!showTranscript) return []
const all = Array.from(conversations.values()).flat()
const all = Array.from(conversations.entries()).flatMap(([agentId, msgs]) => {
const runtime = agents.get(agentId)?.runtime
return msgs.map(msg => msg.runtime ? msg : { ...msg, runtime })
})
return all.sort((a, b) => a.timestamp - b.timestamp)
}, [conversations, showTranscript])
}, [agents, conversations, showTranscript])

// Context menu items
const contextMenuItems = selection.contextMenu ? (
Expand Down Expand Up @@ -326,6 +329,7 @@ export function AgentVisualizer() {
visible={!!selectedAgent}
agentName={selectedAgent?.name ?? ''}
agentState={selectedAgent?.state ?? 'idle'}
agentRuntime={selectedAgent?.runtime}
conversation={selectedConversation}
onClose={selection.clearAgent}
/>
Expand Down
7 changes: 5 additions & 2 deletions web/components/agent-visualizer/message-feed-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
import { Agent, Z, type AgentState } from '@/lib/agent-types'
import { getMessageSenderLabel } from '@/lib/agent-runtime'
import { COLORS, ROLE_COLORS, getStateColor } from '@/lib/colors'
import type { ConversationMessage } from '@/hooks/simulation/types'
import { useClickOutside } from '@/hooks/use-click-outside'
Expand Down Expand Up @@ -285,6 +286,7 @@ export function MessageFeedPanel({
message={msg}
agentId={msg.agentId}
agentName={agents.get(msg.agentId)?.name ?? msg.agentId}
agentRuntime={msg.runtime ?? agents.get(msg.agentId)?.runtime}
showAgent={activeTab === 'all'}
isSelected={selectedAgentId === msg.agentId}
onClick={() => { onAgentClick(msg.agentId); setExpanded(false) }}
Expand Down Expand Up @@ -332,10 +334,11 @@ function TabButton({ label, active, onClick, color, hasUnread }: {

// ── Message Row ──

function MessageRow({ message, agentId, agentName, showAgent, isSelected, onClick }: {
function MessageRow({ message, agentId, agentName, agentRuntime, showAgent, isSelected, onClick }: {
message: ConversationMessage
agentId: string
agentName: string
agentRuntime?: Agent['runtime']
showAgent: boolean
isSelected: boolean
onClick: () => void
Expand All @@ -357,7 +360,7 @@ function MessageRow({ message, agentId, agentName, showAgent, isSelected, onClic
{/* Header row */}
<div className="flex items-center gap-1.5 mb-0.5">
<span className="text-[9px] font-mono font-semibold" style={{ color: role.text + '90' }}>
{role.label}
{getMessageSenderLabel(message.type, message.runtime ?? agentRuntime)}
</span>
{showAgent && (
<span className="text-[9px] font-mono" style={{ color: COLORS.textMuted }}>
Expand Down
27 changes: 23 additions & 4 deletions web/components/agent-visualizer/transcript-message.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use client'

import { useState } from 'react'
import type { Agent } from '@/lib/agent-types'
import { getMessageSenderLabel } from '@/lib/agent-runtime'
import { COLORS } from '@/lib/colors'
import { ToolContentRenderer } from './tool-content-renderer'
import type { ConversationMessage } from '@/hooks/simulation/types'
Expand All @@ -21,8 +23,19 @@ export function HighlightText({ text, query }: { text: string; query?: string })
)
}

export function TranscriptMessage({ message, compact = false, searchQuery }: { message: ConversationMessage; compact?: boolean; searchQuery?: string }) {
export function TranscriptMessage({
message,
compact = false,
searchQuery,
agentRuntime,
}: {
message: ConversationMessage
compact?: boolean
searchQuery?: string
agentRuntime?: Agent['runtime']
}) {
const [expanded, setExpanded] = useState(false)
const runtime = message.runtime ?? agentRuntime

switch (message.type) {
case 'user':
Expand All @@ -34,7 +47,9 @@ export function TranscriptMessage({ message, compact = false, searchQuery }: { m
border: `1px solid ${COLORS.userMsgBorder}`,
}}
>
<div className="text-[9px] mb-1 font-semibold tracking-wider" style={{ color: COLORS.userLabel }}>USER</div>
<div className="text-[9px] mb-1 font-semibold tracking-wider" style={{ color: COLORS.userLabel }}>
{getMessageSenderLabel(message.type, runtime)}
</div>
<div style={{ color: COLORS.userText }} className="whitespace-pre-wrap break-words">
<HighlightText text={message.content} query={searchQuery} />
</div>
Expand All @@ -50,7 +65,9 @@ export function TranscriptMessage({ message, compact = false, searchQuery }: { m
border: `1px solid ${COLORS.holoBorder08}`,
}}
>
<div className="text-[9px] mb-1 font-semibold tracking-wider" style={{ color: COLORS.assistantLabel }}>CLAUDE</div>
<div className="text-[9px] mb-1 font-semibold tracking-wider" style={{ color: COLORS.assistantLabel }}>
{getMessageSenderLabel(message.type, runtime)}
</div>
<div style={{ color: COLORS.assistantText }} className="whitespace-pre-wrap break-words">
<HighlightText text={compact ? message.content.slice(0, 200) + (message.content.length > 200 ? '...' : '') : message.content} query={searchQuery} />
</div>
Expand All @@ -68,7 +85,9 @@ export function TranscriptMessage({ message, compact = false, searchQuery }: { m
onClick={() => setExpanded(!expanded)}
>
<div className="flex items-center gap-1.5">
<span className="text-[9px] font-semibold tracking-wider" style={{ color: COLORS.thinkingLabel }}>THINKING</span>
<span className="text-[9px] font-semibold tracking-wider" style={{ color: COLORS.thinkingLabel }}>
{getMessageSenderLabel(message.type, runtime)}
</span>
<span className="text-[9px]" style={{ color: COLORS.thinkingArrow }}>{expanded ? '▾' : '▸'}</span>
{!expanded && (
<span className="text-[9px] font-mono truncate opacity-50" style={{ color: COLORS.thinkingPreview }}>
Expand Down
1 change: 1 addition & 0 deletions web/hooks/simulation/handle-agent-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function handleAgentSpawn(
state: 'idle',
...(task ? { task } : {}),
...(model ? { tokensMax: ctx.getContextWindowSize(model) } : {}),
...(runtime ? { runtime } : {}),
})
return
}
Expand Down
10 changes: 9 additions & 1 deletion web/hooks/simulation/handle-message-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export function handleMessage(
const agentName = asString(payload.agent)
const content = asString(payload.content)
const role = typeof payload.role === 'string' ? payload.role : undefined
const payloadRuntime = payload.runtime === 'codex'
? 'codex' as const
: payload.runtime === 'claude'
? 'claude' as const
: undefined

// Map role to conversation message type
const msgType: ConversationMessage['type'] =
Expand Down Expand Up @@ -49,12 +54,15 @@ export function handleMessage(
updates.state = 'thinking'
}
}
if (payloadRuntime && msgAgent.runtime !== payloadRuntime) {
updates.runtime = payloadRuntime
}
if (Object.keys(updates).length > 0) {
state.agents.set(agentName, { ...msgAgent, ...updates })
}
}

appendConversation(state.conversations, agentName, { type: msgType, content, timestamp: currentTime })
appendConversation(state.conversations, agentName, { type: msgType, content, timestamp: currentTime, runtime: payloadRuntime ?? msgAgent?.runtime })
}

export function handleContextUpdate(
Expand Down
2 changes: 2 additions & 0 deletions web/hooks/simulation/handle-tool-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export function handleToolCallStart(

appendConversation(state.conversations, agentName, {
type: 'tool_call', content: `> ${toolName} ${args}`, timestamp: currentTime,
runtime: agent.runtime,
toolName, inputData,
})
}
Expand Down Expand Up @@ -166,6 +167,7 @@ export function handleToolCallEnd(
type: 'tool_result',
content: `< ${result}${tokenCost ? ` (${tokenCost} tokens)` : ''}`,
timestamp: currentTime,
runtime: agent.runtime,
toolName,
})
}
Expand Down
1 change: 1 addition & 0 deletions web/hooks/simulation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface ConversationMessage {
type: 'tool_call' | 'tool_result' | 'assistant' | 'user' | 'thinking'
content: string
timestamp: number
runtime?: Agent['runtime']
toolName?: string
inputData?: Record<string, unknown>
}
Expand Down
12 changes: 12 additions & 0 deletions web/lib/agent-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Agent } from './agent-types'

export function getAgentRuntimeLabel(runtime?: Agent['runtime']): string {
return runtime === 'codex' ? 'CODEX' : 'CLAUDE'
}

export function getMessageSenderLabel(type: string, runtime?: Agent['runtime']): string {
if (type === 'user') return 'USER'
if (type === 'thinking') return 'THINKING'
if (type === 'assistant') return getAgentRuntimeLabel(runtime)
return 'TOOL'
}
8 changes: 4 additions & 4 deletions web/lib/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,10 @@ export const COLORS = {

// ─── Role Colors (message feed & bubbles) ───────────────────────────────────

export const ROLE_COLORS: Record<string, { bg: string; bgSelected: string; text: string; label: string }> = {
assistant: { bg: COLORS.roleAssistantBg, bgSelected: COLORS.roleAssistantBgSelected, text: COLORS.roleAssistantText, label: 'CLAUDE' },
thinking: { bg: COLORS.roleThinkingBg, bgSelected: COLORS.roleThinkingBgSelected, text: COLORS.roleThinkingText, label: 'THINKING' },
user: { bg: COLORS.roleUserBg, bgSelected: COLORS.roleUserBgSelected, text: COLORS.roleUserText, label: 'USER' },
export const ROLE_COLORS: Record<string, { bg: string; bgSelected: string; text: string }> = {
assistant: { bg: COLORS.roleAssistantBg, bgSelected: COLORS.roleAssistantBgSelected, text: COLORS.roleAssistantText },
thinking: { bg: COLORS.roleThinkingBg, bgSelected: COLORS.roleThinkingBgSelected, text: COLORS.roleThinkingText },
user: { bg: COLORS.roleUserBg, bgSelected: COLORS.roleUserBgSelected, text: COLORS.roleUserText },
} as const

// ─── Color Helper Functions ──────────────────────────────────────────────────
Expand Down