diff --git a/core/agent/agent.go b/core/agent/agent.go
index 1aa8fdf..3954651 100644
--- a/core/agent/agent.go
+++ b/core/agent/agent.go
@@ -180,6 +180,12 @@ func (a *Agent) Execute(j *types.Job) *types.JobResult {
}()
if j.Obs != nil {
+ if len(j.ConversationHistory) > 0 {
+ m := j.ConversationHistory[len(j.ConversationHistory)-1]
+ j.Obs.Creation = &types.Creation{ ChatCompletionMessage: &m }
+ a.observer.Update(*j.Obs)
+ }
+
j.Result.AddFinalizer(func(ccm []openai.ChatCompletionMessage) {
j.Obs.Completion = &types.Completion{
Conversation: ccm,
diff --git a/core/types/observable.go b/core/types/observable.go
index 74dd348..48844e9 100644
--- a/core/types/observable.go
+++ b/core/types/observable.go
@@ -6,6 +6,7 @@ import (
)
type Creation struct {
+ ChatCompletionMessage *openai.ChatCompletionMessage `json:"chat_completion_message,omitempty"`
ChatCompletionRequest *openai.ChatCompletionRequest `json:"chat_completion_request,omitempty"`
FunctionDefinition *openai.FunctionDefinition `json:"function_definition,omitempty"`
FunctionParams ActionParams `json:"function_params,omitempty"`
diff --git a/webui/react-ui/src/components/CollapsibleRawSections.jsx b/webui/react-ui/src/components/CollapsibleRawSections.jsx
new file mode 100644
index 0000000..559c9cb
--- /dev/null
+++ b/webui/react-ui/src/components/CollapsibleRawSections.jsx
@@ -0,0 +1,103 @@
+import React, { useState } from 'react';
+import hljs from 'highlight.js/lib/core';
+import json from 'highlight.js/lib/languages/json';
+import 'highlight.js/styles/monokai.css';
+
+hljs.registerLanguage('json', json);
+
+export default function CollapsibleRawSections({ container }) {
+ const [showCreation, setShowCreation] = useState(false);
+ const [showProgress, setShowProgress] = useState(false);
+ const [showCompletion, setShowCompletion] = useState(false);
+ const [copied, setCopied] = useState({ creation: false, progress: false, completion: false });
+
+ const handleCopy = (section, data) => {
+ navigator.clipboard.writeText(JSON.stringify(data, null, 2));
+ setCopied(prev => ({ ...prev, [section]: true }));
+ setTimeout(() => setCopied(prev => ({ ...prev, [section]: false })), 1200);
+ };
+
+ return (
+
+ {/* Creation Section */}
+ {container.creation && (
+
+
+ setShowCreation(v => !v)}
+ >
+
+ Creation
+
+
+
+ {showCreation && (
+
+
+
+ )}
+
+ )}
+ {/* Progress Section */}
+ {container.progress && container.progress.length > 0 && (
+
+
+ setShowProgress(v => !v)}
+ >
+
+ Progress
+
+
+
+ {showProgress && (
+
+
+
+ )}
+
+ )}
+ {/* Completion Section */}
+ {container.completion && (
+
+
+ setShowCompletion(v => !v)}
+ >
+
+ Completion
+
+
+
+ {showCompletion && (
+
+
+
+ )}
+
+ )}
+
+ );
+}
+
diff --git a/webui/react-ui/src/pages/AgentStatus.jsx b/webui/react-ui/src/pages/AgentStatus.jsx
index ae787b6..9d98ae6 100644
--- a/webui/react-ui/src/pages/AgentStatus.jsx
+++ b/webui/react-ui/src/pages/AgentStatus.jsx
@@ -1,4 +1,181 @@
import { useState, useEffect } from 'react';
+import CollapsibleRawSections from '../components/CollapsibleRawSections';
+
+function ObservableSummary({ observable }) {
+ // --- CREATION SUMMARIES ---
+ const creation = observable?.creation || {};
+ // ChatCompletionRequest summary
+ let creationChatMsg = '';
+ // Prefer chat_completion_message if present (for jobs/top-level containers)
+ if (creation?.chat_completion_message && creation.chat_completion_message.content) {
+ creationChatMsg = creation.chat_completion_message.content;
+ } else {
+ const messages = creation?.chat_completion_request?.messages;
+ if (Array.isArray(messages) && messages.length > 0) {
+ const lastMsg = messages[messages.length - 1];
+ creationChatMsg = lastMsg?.content || '';
+ }
+ }
+ // FunctionDefinition summary
+ let creationFunctionDef = '';
+ if (creation?.function_definition?.name) {
+ creationFunctionDef = `Function: ${creation.function_definition.name}`;
+ }
+ // FunctionParams summary
+ let creationFunctionParams = '';
+ if (creation?.function_params && Object.keys(creation.function_params).length > 0) {
+ creationFunctionParams = `Params: ${JSON.stringify(creation.function_params)}`;
+ }
+
+ // --- COMPLETION SUMMARIES ---
+ const completion = observable?.completion || {};
+ // ChatCompletionResponse summary
+ let completionChatMsg = '';
+ const chatCompletion = completion?.chat_completion_response;
+ if (
+ chatCompletion &&
+ Array.isArray(chatCompletion.choices) &&
+ chatCompletion.choices.length > 0
+ ) {
+ const lastChoice = chatCompletion.choices[chatCompletion.choices.length - 1];
+ // Prefer tool_call summary if present
+ let toolCallSummary = '';
+ const toolCalls = lastChoice?.message?.tool_calls;
+ if (Array.isArray(toolCalls) && toolCalls.length > 0) {
+ toolCallSummary = toolCalls.map(tc => {
+ let args = '';
+ // For OpenAI-style, arguments are in tc.function.arguments, function name in tc.function.name
+ if (tc.function && tc.function.arguments) {
+ try {
+ args = typeof tc.function.arguments === 'string' ? tc.function.arguments : JSON.stringify(tc.function.arguments);
+ } catch (e) {
+ args = '[Unserializable arguments]';
+ }
+ }
+ const toolName = tc.function?.name || tc.name || 'unknown';
+ return `Tool call: ${toolName}(${args})`;
+ }).join('\n');
+ }
+ completionChatMsg = lastChoice?.message?.content || '';
+ // Attach toolCallSummary to completionChatMsg for rendering
+ if (toolCallSummary) {
+ completionChatMsg = { toolCallSummary, message: completionChatMsg };
+ }
+ // Else, it's just a string
+
+ }
+ // Conversation summary
+ let completionConversation = '';
+ if (Array.isArray(completion?.conversation) && completion.conversation.length > 0) {
+ const lastConv = completion.conversation[completion.conversation.length - 1];
+ completionConversation = lastConv?.content ? `${lastConv.content}` : '';
+ }
+ // ActionResult summary
+ let completionActionResult = '';
+ if (completion?.action_result) {
+ completionActionResult = `Action Result: ${String(completion.action_result).slice(0, 100)}`;
+ }
+ // AgentState summary
+ let completionAgentState = '';
+ if (completion?.agent_state) {
+ completionAgentState = `Agent State: ${JSON.stringify(completion.agent_state)}`;
+ }
+ // Error summary
+ let completionError = '';
+ if (completion?.error) {
+ completionError = `Error: ${completion.error}`;
+ }
+
+ // Only show if any summary is present
+ if (!creationChatMsg && !creationFunctionDef && !creationFunctionParams &&
+ !completionChatMsg && !completionConversation && !completionActionResult && !completionAgentState && !completionError) {
+ return null;
+ }
+
+ return (
+
+ {/* CREATION */}
+ {creationChatMsg && (
+
+
+ {creationChatMsg}
+
+ )}
+ {creationFunctionDef && (
+
+
+ {creationFunctionDef}
+
+ )}
+ {creationFunctionParams && (
+
+
+ {creationFunctionParams}
+
+ )}
+ {/* COMPLETION */}
+ {/* COMPLETION: Tool call summary if present */}
+ {completionChatMsg && typeof completionChatMsg === 'object' && completionChatMsg.toolCallSummary && (
+
+
+ {completionChatMsg.toolCallSummary}
+
+ )}
+ {/* COMPLETION: Message content if present */}
+ {completionChatMsg && ((typeof completionChatMsg === 'object' && completionChatMsg.message) || typeof completionChatMsg === 'string') && (
+
+
+ {typeof completionChatMsg === 'object' ? completionChatMsg.message : completionChatMsg}
+
+ )}
+ {completionConversation && (
+
+
+ {completionConversation}
+
+ )}
+ {completionActionResult && (
+
+
+ {completionActionResult}
+
+ )}
+ {completionAgentState && (
+
+
+ {completionAgentState}
+
+ )}
+ {completionError && (
+
+
+ {completionError}
+
+ )}
+
+ );
+}
+
import { useParams, Link } from 'react-router-dom';
import hljs from 'highlight.js/lib/core';
import json from 'highlight.js/lib/languages/json';
@@ -7,7 +184,7 @@ import 'highlight.js/styles/monokai.css';
hljs.registerLanguage('json', json);
function AgentStatus() {
- const [showStatus, setShowStatus] = useState(true);
+ const [showStatus, setShowStatus] = useState(false);
const { name } = useParams();
const [statusData, setStatusData] = useState(null);
const [loading, setLoading] = useState(true);
@@ -94,17 +271,16 @@ function AgentStatus() {
setObservableMap(prevMap => {
const prev = prevMap[data.id] || {};
const updated = {
- ...prev,
...data,
- creation: data.creation,
- progress: data.progress,
- completion: data.completion,
+ ...prev,
};
// Events can be received out of order
if (data.creation)
updated.creation = data.creation;
if (data.completion)
updated.completion = data.completion;
+ if ((data.progress?.length ?? 0) > (prev.progress?.length ?? 0))
+ updated.progress = data.progress;
if (data.parent_id && !prevMap[data.parent_id])
prevMap[data.parent_id] = {
id: data.parent_id,
@@ -252,12 +428,17 @@ function AgentStatus() {
setExpandedCards(new Map(expandedCards).set(container.id, newExpanded));
}}
>
-
-
-
- {container.name}#{container.id}
-
-
+
+
+
+
+
+ {container.name}#{container.id}
+
+
+
+
+
- {
const newExpanded = !expandedCards.get(childKey);
setExpandedCards(new Map(expandedCards).set(childKey, newExpanded));
}}
>
-
-
-
- {child.name}#{child.id}
-
-
+
+
+
+
+
+ {child.name}#{child.id}
+
+
+
+
+
- {child.creation && (
-
- )}
- {child.progress && child.progress.length > 0 && (
-
- )}
- {child.completion && (
-
- )}
+
);
})}
)}
- {container.creation && (
-
- )}
- {container.progress && container.progress.length > 0 && (
-
- )}
- {container.completion && (
-
- )}
+