mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(codex): refine tool activity display
This commit is contained in:
@@ -59,9 +59,13 @@ const MILESTONE_TOOL_NAMES = new Set([
|
||||
'Skill',
|
||||
'spawn_agent',
|
||||
'send_input',
|
||||
'send_message',
|
||||
'resume_agent',
|
||||
'followup_task',
|
||||
'wait_agent',
|
||||
'close_agent'
|
||||
'close_agent',
|
||||
'interrupt_agent',
|
||||
'list_agents'
|
||||
])
|
||||
|
||||
const INTERACTIVE_TOOL_NAMES = new Set([
|
||||
|
||||
@@ -4,9 +4,13 @@ import { getInputStringAny, truncate } from '@/lib/toolInputUtils'
|
||||
export const codexAgentToolNames = [
|
||||
'spawn_agent',
|
||||
'send_input',
|
||||
'send_message',
|
||||
'resume_agent',
|
||||
'followup_task',
|
||||
'wait_agent',
|
||||
'close_agent'
|
||||
'close_agent',
|
||||
'interrupt_agent',
|
||||
'list_agents'
|
||||
] as const
|
||||
|
||||
export type CodexAgentToolName = typeof codexAgentToolNames[number]
|
||||
@@ -200,6 +204,9 @@ export function getCodexAgentFieldRows(toolName: string, input: unknown): Array<
|
||||
|
||||
const timeout = typeof input.timeout_ms === 'number' ? `${input.timeout_ms} ms` : null
|
||||
if (timeout) rows.push({ label: 'Timeout', value: timeout })
|
||||
|
||||
const pathPrefix = asNonEmptyString(input.path_prefix)
|
||||
if (pathPrefix) rows.push({ label: 'Path prefix', value: pathPrefix })
|
||||
}
|
||||
|
||||
const targets = getCodexAgentTargets(input)
|
||||
@@ -216,6 +223,7 @@ export function getCodexAgentFieldRows(toolName: string, input: unknown): Array<
|
||||
export type CodexSpawnAgentResult = {
|
||||
agentId: string | null
|
||||
nickname: string | null
|
||||
taskName: string | null
|
||||
}
|
||||
|
||||
export function parseCodexSpawnAgentResult(result: unknown): CodexSpawnAgentResult | null {
|
||||
@@ -224,9 +232,10 @@ export function parseCodexSpawnAgentResult(result: unknown): CodexSpawnAgentResu
|
||||
|
||||
const agentId = asNonEmptyString(obj.agent_id) ?? asNonEmptyString(obj.agentId) ?? asNonEmptyString(obj.id)
|
||||
const nickname = asNonEmptyString(obj.nickname) ?? asNonEmptyString(obj.name)
|
||||
const taskName = asNonEmptyString(obj.task_name) ?? asNonEmptyString(obj.taskName)
|
||||
|
||||
if (!agentId && !nickname) return null
|
||||
return { agentId, nickname }
|
||||
if (!agentId && !nickname && !taskName) return null
|
||||
return { agentId, nickname, taskName }
|
||||
}
|
||||
|
||||
export type CodexAgentStatus = {
|
||||
@@ -236,10 +245,12 @@ export type CodexAgentStatus = {
|
||||
}
|
||||
|
||||
function extractStatusText(value: unknown): string | null {
|
||||
if (typeof value === 'string') return value
|
||||
if (typeof value === 'string') {
|
||||
return normalizeStatusState(value) ? null : value
|
||||
}
|
||||
if (!isObject(value)) return null
|
||||
|
||||
const candidates = ['completed', 'failed', 'error', 'message', 'output', 'text', 'reason']
|
||||
const candidates = ['completed', 'errored', 'failed', 'error', 'message', 'output', 'text', 'reason']
|
||||
for (const key of candidates) {
|
||||
const candidate = value[key]
|
||||
if (typeof candidate === 'string') return candidate
|
||||
@@ -248,19 +259,51 @@ function extractStatusText(value: unknown): string | null {
|
||||
return safeStringify(value)
|
||||
}
|
||||
|
||||
const CODEX_AGENT_STATUS_STATES = new Set([
|
||||
'completed',
|
||||
'errored',
|
||||
'failed',
|
||||
'error',
|
||||
'canceled',
|
||||
'cancelled',
|
||||
'killed',
|
||||
'running',
|
||||
'pending',
|
||||
'pending_init',
|
||||
'interrupted',
|
||||
'shutdown',
|
||||
'not_found'
|
||||
])
|
||||
|
||||
function normalizeStatusState(value: string): string | null {
|
||||
const state = value
|
||||
.trim()
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
||||
.toLowerCase()
|
||||
.replace(/[\s-]+/g, '_')
|
||||
if (!CODEX_AGENT_STATUS_STATES.has(state)) return null
|
||||
if (state === 'cancelled') return 'canceled'
|
||||
if (state === 'errored') return 'error'
|
||||
return state
|
||||
}
|
||||
|
||||
function extractStatusState(value: unknown): string {
|
||||
if (typeof value === 'string') return normalizeStatusState(value) ?? 'completed'
|
||||
if (!isObject(value)) return 'completed'
|
||||
|
||||
const states = ['completed', 'failed', 'error', 'canceled', 'cancelled', 'killed', 'running', 'pending']
|
||||
for (const state of states) {
|
||||
if (state in value) return state === 'cancelled' ? 'canceled' : state
|
||||
for (const state of CODEX_AGENT_STATUS_STATES) {
|
||||
if (state in value) return normalizeStatusState(state) ?? state
|
||||
}
|
||||
|
||||
const status = asNonEmptyString(value.status) ?? asNonEmptyString(value.state)
|
||||
return status ?? 'completed'
|
||||
return status ? normalizeStatusState(status) ?? status : 'completed'
|
||||
}
|
||||
|
||||
export function parseCodexWaitAgentResult(result: unknown): { statuses: CodexAgentStatus[]; timedOut: boolean | null } | null {
|
||||
export function parseCodexWaitAgentResult(result: unknown): {
|
||||
statuses: CodexAgentStatus[]
|
||||
timedOut: boolean | null
|
||||
message: string | null
|
||||
} | null {
|
||||
const obj = parseMaybeJsonObject(result)
|
||||
if (!obj) return null
|
||||
|
||||
@@ -282,19 +325,22 @@ export function parseCodexWaitAgentResult(result: unknown): { statuses: CodexAge
|
||||
: typeof obj.timedOut === 'boolean'
|
||||
? obj.timedOut
|
||||
: null
|
||||
const message = asNonEmptyString(obj.message)
|
||||
|
||||
if (statuses.length === 0 && timedOut === null) return null
|
||||
return { statuses, timedOut }
|
||||
if (statuses.length === 0 && timedOut === null && !message) return null
|
||||
return { statuses, timedOut, message }
|
||||
}
|
||||
|
||||
export function parseCodexCloseAgentResult(result: unknown): CodexAgentStatus | null {
|
||||
const obj = parseMaybeJsonObject(result)
|
||||
if (!obj) return null
|
||||
|
||||
const previousStatus = isObject(obj.previous_status) ? obj.previous_status
|
||||
: isObject(obj.previousStatus) ? obj.previousStatus
|
||||
const previousStatus = Object.prototype.hasOwnProperty.call(obj, 'previous_status')
|
||||
? obj.previous_status
|
||||
: Object.prototype.hasOwnProperty.call(obj, 'previousStatus')
|
||||
? obj.previousStatus
|
||||
: null
|
||||
if (!previousStatus) return null
|
||||
if (previousStatus === null || previousStatus === undefined) return null
|
||||
|
||||
return {
|
||||
agentId: '',
|
||||
@@ -303,13 +349,36 @@ export function parseCodexCloseAgentResult(result: unknown): CodexAgentStatus |
|
||||
}
|
||||
}
|
||||
|
||||
export function parseCodexListAgentsResult(result: unknown): CodexAgentStatus[] | null {
|
||||
const obj = parseMaybeJsonObject(result)
|
||||
if (!obj || !Array.isArray(obj.agents)) return null
|
||||
|
||||
const agents = obj.agents.flatMap((value): CodexAgentStatus[] => {
|
||||
if (!isObject(value)) return []
|
||||
const agentId = asNonEmptyString(value.agent_name)
|
||||
?? asNonEmptyString(value.agentName)
|
||||
?? asNonEmptyString(value.agent_id)
|
||||
?? asNonEmptyString(value.agentId)
|
||||
if (!agentId) return []
|
||||
const status = value.agent_status ?? value.agentStatus ?? value.status
|
||||
return [{
|
||||
agentId,
|
||||
state: extractStatusState(status),
|
||||
text: extractStatusText(status)
|
||||
}]
|
||||
})
|
||||
|
||||
return agents
|
||||
}
|
||||
|
||||
export function summarizeCodexAgentResult(toolName: string, result: unknown): string | null {
|
||||
if (toolName === 'spawn_agent') {
|
||||
const parsed = parseCodexSpawnAgentResult(result)
|
||||
if (!parsed) return null
|
||||
const label = parsed.nickname && parsed.agentId
|
||||
? `${parsed.nickname} (${parsed.agentId})`
|
||||
: parsed.nickname ?? parsed.agentId
|
||||
const reference = parsed.taskName ?? parsed.agentId
|
||||
const label = parsed.nickname && reference
|
||||
? `${parsed.nickname} (${reference})`
|
||||
: parsed.nickname ?? reference
|
||||
return label ? `Launched ${label}` : 'Agent launched'
|
||||
}
|
||||
|
||||
@@ -317,6 +386,7 @@ export function summarizeCodexAgentResult(toolName: string, result: unknown): st
|
||||
const parsed = parseCodexWaitAgentResult(result)
|
||||
if (!parsed) return null
|
||||
if (parsed.statuses.length === 0 && parsed.timedOut) return 'Timed out'
|
||||
if (parsed.statuses.length === 0 && parsed.message) return parsed.message
|
||||
const completed = parsed.statuses.filter((status) => status.state === 'completed').length
|
||||
const failed = parsed.statuses.filter((status) => status.state !== 'completed').length
|
||||
const parts = []
|
||||
@@ -326,10 +396,20 @@ export function summarizeCodexAgentResult(toolName: string, result: unknown): st
|
||||
return parts.length > 0 ? parts.join(', ') : 'No agent status yet'
|
||||
}
|
||||
|
||||
if (toolName === 'close_agent') {
|
||||
if (toolName === 'close_agent' || toolName === 'interrupt_agent') {
|
||||
const parsed = parseCodexCloseAgentResult(result)
|
||||
if (!parsed) return null
|
||||
return `Closed (${parsed.state})`
|
||||
return `${toolName === 'interrupt_agent' ? 'Interrupted' : 'Closed'} (${parsed.state})`
|
||||
}
|
||||
|
||||
if (toolName === 'list_agents') {
|
||||
const agents = parseCodexListAgentsResult(result)
|
||||
if (!agents) return null
|
||||
if (agents.length === 0) return 'No live agents'
|
||||
const running = agents.filter((agent) => agent.state === 'running').length
|
||||
return running > 0
|
||||
? `${agents.length} live, ${running} running`
|
||||
: `${agents.length} live agent${agents.length === 1 ? '' : 's'}`
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -214,6 +214,88 @@ describe('getToolPresentation — Codex agent tools', () => {
|
||||
expect(presentation.subtitle).not.toContain('hidden child output')
|
||||
expect(presentation.minimal).toBe(true)
|
||||
})
|
||||
|
||||
it('presents MultiAgent V2 messaging tools by intent', () => {
|
||||
const message = getToolPresentation({
|
||||
toolName: 'send_message',
|
||||
input: { target: '/root/review', message: 'Status?' },
|
||||
result: '',
|
||||
childrenCount: 0,
|
||||
description: null,
|
||||
metadata: null,
|
||||
})
|
||||
const followup = getToolPresentation({
|
||||
toolName: 'followup_task',
|
||||
input: { target: '/root/review', message: 'Run tests' },
|
||||
result: '',
|
||||
childrenCount: 0,
|
||||
description: null,
|
||||
metadata: null,
|
||||
})
|
||||
|
||||
expect(message).toMatchObject({ title: 'Message agent', subtitle: '/root/review', minimal: true })
|
||||
expect(followup).toMatchObject({ title: 'Follow up agent', subtitle: '/root/review', minimal: true })
|
||||
})
|
||||
|
||||
it('summarizes list_agents and interrupt_agent results', () => {
|
||||
const list = getToolPresentation({
|
||||
toolName: 'list_agents',
|
||||
input: {},
|
||||
result: JSON.stringify({
|
||||
agents: [
|
||||
{ agent_name: '/root/a', agent_status: 'running' },
|
||||
{ agent_name: '/root/b', agent_status: { completed: 'done' } }
|
||||
]
|
||||
}),
|
||||
childrenCount: 0,
|
||||
description: null,
|
||||
metadata: null,
|
||||
})
|
||||
const interrupt = getToolPresentation({
|
||||
toolName: 'interrupt_agent',
|
||||
input: { target: '/root/a' },
|
||||
result: '{"previous_status":"running"}',
|
||||
childrenCount: 0,
|
||||
description: null,
|
||||
metadata: null,
|
||||
})
|
||||
|
||||
expect(list).toMatchObject({ title: 'List agents', subtitle: '2 live, 1 running' })
|
||||
expect(interrupt).toMatchObject({ title: 'Interrupt agent', subtitle: 'Interrupted (running)' })
|
||||
})
|
||||
|
||||
it('uses MultiAgent V2 result fields and status variants', () => {
|
||||
const spawn = getToolPresentation({
|
||||
toolName: 'spawn_agent',
|
||||
input: { task_name: 'review' },
|
||||
result: JSON.stringify({ task_name: '/root/review', nickname: 'Reviewer' }),
|
||||
childrenCount: 0,
|
||||
description: null,
|
||||
metadata: null,
|
||||
})
|
||||
const wait = getToolPresentation({
|
||||
toolName: 'wait_agent',
|
||||
input: { timeout_ms: 1000 },
|
||||
result: JSON.stringify({ message: 'Wait completed.', timed_out: false }),
|
||||
childrenCount: 0,
|
||||
description: null,
|
||||
metadata: null,
|
||||
})
|
||||
const list = getToolPresentation({
|
||||
toolName: 'list_agents',
|
||||
input: {},
|
||||
result: JSON.stringify({
|
||||
agents: [{ agent_name: '/root/review', agent_status: { errored: 'test failed' } }]
|
||||
}),
|
||||
childrenCount: 0,
|
||||
description: null,
|
||||
metadata: null,
|
||||
})
|
||||
|
||||
expect(spawn.subtitle).toBe('Launched Reviewer (/root/review)')
|
||||
expect(wait.subtitle).toBe('Wait completed.')
|
||||
expect(list.subtitle).toBe('1 live agent')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getToolPresentation — native titles', () => {
|
||||
|
||||
@@ -355,6 +355,15 @@ export const knownTools: Record<string, {
|
||||
},
|
||||
minimal: true
|
||||
},
|
||||
send_message: {
|
||||
icon: () => <MessageSquareIcon className={DEFAULT_ICON_CLASS} />,
|
||||
title: () => 'Message agent',
|
||||
subtitle: (opts) => {
|
||||
const targets = getCodexAgentTargets(opts.input)
|
||||
return targets.length > 0 ? targets.join(', ') : 'Queued message'
|
||||
},
|
||||
minimal: true
|
||||
},
|
||||
resume_agent: {
|
||||
icon: () => <RocketIcon className={DEFAULT_ICON_CLASS} />,
|
||||
title: () => 'Resume agent',
|
||||
@@ -364,6 +373,15 @@ export const knownTools: Record<string, {
|
||||
},
|
||||
minimal: true
|
||||
},
|
||||
followup_task: {
|
||||
icon: () => <MessageSquareIcon className={DEFAULT_ICON_CLASS} />,
|
||||
title: () => 'Follow up agent',
|
||||
subtitle: (opts) => {
|
||||
const targets = getCodexAgentTargets(opts.input)
|
||||
return targets.length > 0 ? targets.join(', ') : null
|
||||
},
|
||||
minimal: true
|
||||
},
|
||||
wait_agent: {
|
||||
icon: () => <RocketIcon className={DEFAULT_ICON_CLASS} />,
|
||||
title: (opts) => {
|
||||
@@ -389,6 +407,24 @@ export const knownTools: Record<string, {
|
||||
},
|
||||
minimal: true
|
||||
},
|
||||
interrupt_agent: {
|
||||
icon: () => <RocketIcon className={DEFAULT_ICON_CLASS} />,
|
||||
title: () => 'Interrupt agent',
|
||||
subtitle: (opts) => {
|
||||
const summary = summarizeCodexAgentResult(opts.toolName, opts.result)
|
||||
if (summary) return summary
|
||||
const targets = getCodexAgentTargets(opts.input)
|
||||
return targets.length > 0 ? targets.join(', ') : null
|
||||
},
|
||||
minimal: true
|
||||
},
|
||||
list_agents: {
|
||||
icon: () => <UsersIcon className={DEFAULT_ICON_CLASS} />,
|
||||
title: () => 'List agents',
|
||||
subtitle: (opts) => summarizeCodexAgentResult(opts.toolName, opts.result)
|
||||
?? getInputStringAny(opts.input, ['path_prefix']),
|
||||
minimal: true
|
||||
},
|
||||
CodexReasoning: {
|
||||
icon: () => <BulbIcon className={DEFAULT_ICON_CLASS} />,
|
||||
title: (opts) => getInputStringAny(opts.input, ['title']) ?? 'Reasoning',
|
||||
|
||||
@@ -84,9 +84,13 @@ export const toolViewRegistry: Record<string, ToolViewComponent> = {
|
||||
CodexAgent: CodexAgentView,
|
||||
spawn_agent: CodexAgentView,
|
||||
send_input: CodexAgentView,
|
||||
send_message: CodexAgentView,
|
||||
resume_agent: CodexAgentView,
|
||||
followup_task: CodexAgentView,
|
||||
wait_agent: CodexAgentView,
|
||||
close_agent: CodexAgentView,
|
||||
interrupt_agent: CodexAgentView,
|
||||
list_agents: CodexAgentView,
|
||||
AskUserQuestion: AskUserQuestionView,
|
||||
ExitPlanMode: ExitPlanModeView,
|
||||
CursorAskQuestion: AskUserQuestionView,
|
||||
@@ -106,9 +110,13 @@ export const toolFullViewRegistry: Record<string, ToolViewComponent> = {
|
||||
Skill: SkillFullView,
|
||||
spawn_agent: CodexAgentView,
|
||||
send_input: CodexAgentView,
|
||||
send_message: CodexAgentView,
|
||||
resume_agent: CodexAgentView,
|
||||
followup_task: CodexAgentView,
|
||||
wait_agent: CodexAgentView,
|
||||
close_agent: CodexAgentView,
|
||||
interrupt_agent: CodexAgentView,
|
||||
list_agents: CodexAgentView,
|
||||
AskUserQuestion: AskUserQuestionView,
|
||||
ExitPlanMode: ExitPlanModeView,
|
||||
CursorAskQuestion: AskUserQuestionView,
|
||||
|
||||
@@ -153,6 +153,9 @@ describe('getToolResultViewComponent registry', () => {
|
||||
it('uses a dedicated result view for Codex agent tools', () => {
|
||||
expect(getToolResultViewComponent('spawn_agent')).not.toBe(getToolResultViewComponent('SomeUnknownTool'))
|
||||
expect(getToolResultViewComponent('wait_agent')).toBe(getToolResultViewComponent('spawn_agent'))
|
||||
expect(getToolResultViewComponent('followup_task')).toBe(getToolResultViewComponent('spawn_agent'))
|
||||
expect(getToolResultViewComponent('interrupt_agent')).toBe(getToolResultViewComponent('spawn_agent'))
|
||||
expect(getToolResultViewComponent('list_agents')).toBe(getToolResultViewComponent('spawn_agent'))
|
||||
})
|
||||
|
||||
it('Agent falls back to GenericResultView (no dedicated view — view layer must not filter content)', () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getCodexAgentActivity,
|
||||
getCodexAgentTargets,
|
||||
parseCodexCloseAgentResult,
|
||||
parseCodexListAgentsResult,
|
||||
parseCodexSpawnAgentResult,
|
||||
parseCodexWaitAgentResult
|
||||
} from '@/components/ToolCard/codexAgents'
|
||||
@@ -791,6 +792,7 @@ const CodexAgentResultView: ToolViewComponent = (props: ToolViewProps) => {
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ResultStatusPill text="Agent launched" />
|
||||
{parsed.nickname ? <AgentIdPill label="Name" value={parsed.nickname} /> : null}
|
||||
{parsed.taskName ? <AgentIdPill label="Task" value={parsed.taskName} /> : null}
|
||||
{parsed.agentId ? <AgentIdPill label="ID" value={parsed.agentId} /> : null}
|
||||
{showDetails ? <RawJsonDevOnly value={result} surface={props.surface} /> : null}
|
||||
</div>
|
||||
@@ -802,7 +804,7 @@ const CodexAgentResultView: ToolViewComponent = (props: ToolViewProps) => {
|
||||
const parsed = parseCodexWaitAgentResult(result)
|
||||
if (parsed) {
|
||||
if (parsed.statuses.length === 0) {
|
||||
return <ResultStatusPill text={parsed.timedOut ? 'Timed out' : 'No status'} />
|
||||
return <ResultStatusPill text={parsed.timedOut ? 'Timed out' : parsed.message ?? 'No status'} />
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -840,14 +842,14 @@ const CodexAgentResultView: ToolViewComponent = (props: ToolViewProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'close_agent') {
|
||||
if (name === 'close_agent' || name === 'interrupt_agent') {
|
||||
const parsed = parseCodexCloseAgentResult(result)
|
||||
if (parsed) {
|
||||
const targets = getCodexAgentTargets(input)
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ResultStatusPill text="Agent closed" />
|
||||
<ResultStatusPill text={name === 'interrupt_agent' ? 'Agent interrupted' : 'Agent closed'} />
|
||||
{targets[0] ? <AgentIdPill label="ID" value={targets[0]} /> : null}
|
||||
<ResultStatusPill text={parsed.state} />
|
||||
</div>
|
||||
@@ -862,6 +864,39 @@ const CodexAgentResultView: ToolViewComponent = (props: ToolViewProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'list_agents') {
|
||||
const agents = parseCodexListAgentsResult(result)
|
||||
if (agents) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ResultStatusPill text={`${agents.length} live agent${agents.length === 1 ? '' : 's'}`} />
|
||||
{Object.entries(agents.reduce<Record<string, number>>((counts, agent) => {
|
||||
counts[agent.state] = (counts[agent.state] ?? 0) + 1
|
||||
return counts
|
||||
}, {})).map(([status, count]) => (
|
||||
<ResultStatusPill key={status} text={`${count} ${status}`} />
|
||||
))}
|
||||
</div>
|
||||
{showDetails ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
{agents.map((agent) => (
|
||||
<div key={agent.agentId} className="border-b border-[var(--app-border)] py-2 last:border-b-0">
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs text-[var(--app-hint)]">
|
||||
<ResultStatusPill text={agent.state} />
|
||||
<span className="font-mono break-all">{agent.agentId}</span>
|
||||
</div>
|
||||
{agent.text ? <div className="mt-1 text-sm text-[var(--app-fg)]">{agent.text}</div> : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{showDetails ? <RawJsonDevOnly value={result} surface={props.surface} /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const text = extractTextFromResult(result)
|
||||
if (text) {
|
||||
if (!showDetails) {
|
||||
@@ -980,9 +1015,13 @@ export const toolResultViewRegistry: Record<string, ToolViewComponent> = {
|
||||
Skill: SkillResultView,
|
||||
spawn_agent: CodexAgentResultView,
|
||||
send_input: CodexAgentResultView,
|
||||
send_message: CodexAgentResultView,
|
||||
resume_agent: CodexAgentResultView,
|
||||
followup_task: CodexAgentResultView,
|
||||
wait_agent: CodexAgentResultView,
|
||||
close_agent: CodexAgentResultView,
|
||||
interrupt_agent: CodexAgentResultView,
|
||||
list_agents: CodexAgentResultView,
|
||||
AskUserQuestion: AskUserQuestionResultView,
|
||||
ExitPlanMode: MarkdownResultView,
|
||||
ask_user_question: AskUserQuestionResultView,
|
||||
|
||||
Reference in New Issue
Block a user