Skip to content

Commit cfbe0f8

Browse files
committed
Refactor session status handling and update frontend display logic
- Removed the finalOutput field from the AgenticSessionStatus type and related CRD, streamlining session status management. - Updated the ProjectSessionDetailPage to display results using the new result field instead of finalOutput, enhancing clarity in session details. - Simplified the status update logic in the SimpleClaudeRunner by eliminating the finalOutput parameter, focusing on essential status fields. - Improved message handling in the frontend to align with the updated session status structure, ensuring a consistent user experience.
1 parent f24a0ee commit cfbe0f8

File tree

7 files changed

+78
-289
lines changed

7 files changed

+78
-289
lines changed

components/backend/handlers.go

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,6 @@ func listSessions(c *gin.Context) {
438438

439439
if status, ok := item.Object["status"].(map[string]interface{}); ok {
440440
session.Status = parseStatus(status)
441-
if session.Status != nil {
442-
enrichStatusFromContentService(c, project, item.GetName(), session.Status)
443-
}
444441
}
445442

446443
sessions = append(sessions, session)
@@ -798,11 +795,6 @@ func getSession(c *gin.Context) {
798795
session.Status = parseStatus(status)
799796
}
800797

801-
// Augment from PVC via content service when possible
802-
if session.Status != nil {
803-
enrichStatusFromContentService(c, project, sessionName, session.Status)
804-
}
805-
806798
c.JSON(http.StatusOK, session)
807799
}
808800

@@ -924,77 +916,6 @@ func mergeGitConfigs(userConfig, defaultConfig *GitConfig) *GitConfig {
924916
return merged
925917
}
926918

927-
// Read session data from persistent files and populate status
928-
func readDataFromFiles(sessionName string, status *AgenticSessionStatus) {
929-
sessionDir := filepath.Join(stateBaseDir, sessionName)
930-
status.StateDir = sessionDir
931-
finalOutputFile := filepath.Join(sessionDir, "final-output.txt")
932-
if finalOutputBytes, err := ioutil.ReadFile(finalOutputFile); err == nil {
933-
status.FinalOutput = string(finalOutputBytes)
934-
}
935-
936-
messagesFile := filepath.Join(sessionDir, "messages.json")
937-
if messagesBytes, err := ioutil.ReadFile(messagesFile); err == nil {
938-
var messages []MessageObject
939-
if err := json.Unmarshal(messagesBytes, &messages); err == nil {
940-
status.Messages = messages
941-
status.MessagesCount = len(messages)
942-
} else {
943-
log.Printf("Warning: failed to unmarshal messages for %s: %v", sessionName, err)
944-
}
945-
}
946-
947-
// Count artifacts under sessionDir/artifacts if present
948-
status.ArtifactsCount = countArtifacts(filepath.Join(sessionDir, "artifacts"))
949-
}
950-
951-
// Enrich status by fetching PVC-backed files via the per-namespace content service
952-
func enrichStatusFromContentService(c *gin.Context, project string, sessionName string, status *AgenticSessionStatus) {
953-
base := os.Getenv("CONTENT_SERVICE_BASE")
954-
if base == "" {
955-
base = "http://ambient-content.%s.svc:8080"
956-
}
957-
endpoint := fmt.Sprintf(base, project)
958-
959-
httpGet := func(path string) (string, error) {
960-
u := fmt.Sprintf("%s/content/file?path=%s", endpoint, url.QueryEscape(path))
961-
req, _ := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, u, nil)
962-
if auth := c.GetHeader("Authorization"); strings.TrimSpace(auth) != "" {
963-
req.Header.Set("Authorization", auth)
964-
}
965-
resp, err := http.DefaultClient.Do(req)
966-
if err != nil {
967-
return "", err
968-
}
969-
defer resp.Body.Close()
970-
if resp.StatusCode != http.StatusOK {
971-
return "", fmt.Errorf("status %d", resp.StatusCode)
972-
}
973-
b, _ := ioutil.ReadAll(resp.Body)
974-
return string(b), nil
975-
}
976-
977-
fetchedAny := false
978-
979-
if s, err := httpGet(fmt.Sprintf("/sessions/%s/messages.json", sessionName)); err == nil {
980-
var messages []MessageObject
981-
if jerr := json.Unmarshal([]byte(s), &messages); jerr == nil {
982-
status.Messages = messages
983-
status.MessagesCount = len(messages)
984-
fetchedAny = true
985-
}
986-
}
987-
if s, err := httpGet(fmt.Sprintf("/sessions/%s/final-output.txt", sessionName)); err == nil {
988-
status.FinalOutput = s
989-
fetchedAny = true
990-
}
991-
992-
// Fallback to legacy on-pod files if content service unavailable
993-
if !fetchedAny {
994-
readDataFromFiles(sessionName, status)
995-
}
996-
}
997-
998919
// countArtifacts recursively counts files under the provided directory (returns 0 if missing)
999920
func countArtifacts(artifactsDir string) int {
1000921
info, err := os.Stat(artifactsDir)
@@ -1491,7 +1412,7 @@ func updateSessionStatus(c *gin.Context) {
14911412

14921413
// Accept standard fields and result summary fields from runner
14931414
allowed := map[string]struct{}{
1494-
"phase": {}, "completionTime": {}, "cost": {}, "finalOutput": {}, "message": {},
1415+
"phase": {}, "completionTime": {}, "cost": {}, "message": {},
14951416
"subtype": {}, "duration_ms": {}, "duration_api_ms": {}, "is_error": {},
14961417
"num_turns": {}, "session_id": {}, "total_cost_usd": {}, "usage": {}, "result": {},
14971418
}
@@ -1539,10 +1460,6 @@ func proxyContentWrites(c *gin.Context, project, sessionName string, statusUpdat
15391460
// Serialize writes we care about
15401461
writes := []writeReq{}
15411462

1542-
if v, ok := statusUpdate["finalOutput"].(string); ok && v != "" {
1543-
writes = append(writes, writeReq{Path: fmt.Sprintf("/sessions/%s/final-output.txt", sessionName), Content: v, Encoding: "utf8"})
1544-
delete(statusUpdate, "finalOutput")
1545-
}
15461463
if msgs, ok := statusUpdate["messages"].([]interface{}); ok && len(msgs) > 0 {
15471464
if b, err := json.Marshal(msgs); err == nil {
15481465
writes = append(writes, writeReq{Path: fmt.Sprintf("/sessions/%s/messages.json", sessionName), Content: string(b), Encoding: "utf8"})

components/backend/main.go

Lines changed: 14 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -298,27 +298,20 @@ type MessageObject struct {
298298
}
299299

300300
type AgenticSessionStatus struct {
301-
Phase string `json:"phase,omitempty"`
302-
Message string `json:"message,omitempty"`
303-
StartTime *string `json:"startTime,omitempty"`
304-
CompletionTime *string `json:"completionTime,omitempty"`
305-
JobName string `json:"jobName,omitempty"`
306-
FinalOutput string `json:"finalOutput,omitempty"`
307-
Cost *float64 `json:"cost,omitempty"`
308-
Messages []MessageObject `json:"messages,omitempty"`
309-
StateDir string `json:"stateDir,omitempty"`
310-
ArtifactsCount int `json:"artifactsCount,omitempty"`
311-
MessagesCount int `json:"messagesCount,omitempty"`
301+
Phase string `json:"phase,omitempty"`
302+
Message string `json:"message,omitempty"`
303+
StartTime *string `json:"startTime,omitempty"`
304+
CompletionTime *string `json:"completionTime,omitempty"`
305+
JobName string `json:"jobName,omitempty"`
306+
StateDir string `json:"stateDir,omitempty"`
312307
// Result summary fields from runner
313-
Subtype string `json:"subtype,omitempty"`
314-
DurationMs int `json:"duration_ms,omitempty"`
315-
DurationApiMs int `json:"duration_api_ms,omitempty"`
316-
IsError bool `json:"is_error,omitempty"`
317-
NumTurns int `json:"num_turns,omitempty"`
318-
SessionID string `json:"session_id,omitempty"`
319-
TotalCostUSD *float64 `json:"total_cost_usd,omitempty"`
320-
Usage map[string]interface{} `json:"usage,omitempty"`
321-
Result *string `json:"result,omitempty"`
308+
Subtype string `json:"subtype,omitempty"`
309+
IsError bool `json:"is_error,omitempty"`
310+
NumTurns int `json:"num_turns,omitempty"`
311+
SessionID string `json:"session_id,omitempty"`
312+
TotalCostUSD *float64 `json:"total_cost_usd,omitempty"`
313+
Usage map[string]interface{} `json:"usage,omitempty"`
314+
Result *string `json:"result,omitempty"`
322315
}
323316

324317
type CreateAgenticSessionRequest struct {
@@ -758,87 +751,11 @@ func parseStatus(status map[string]interface{}) *AgenticSessionStatus {
758751
result.JobName = jobName
759752
}
760753

761-
if finalOutput, ok := status["finalOutput"].(string); ok {
762-
result.FinalOutput = finalOutput
763-
}
764-
765-
if cost, ok := status["cost"].(float64); ok {
766-
result.Cost = &cost
767-
}
768-
769-
if messages, ok := status["messages"].([]interface{}); ok {
770-
result.Messages = make([]MessageObject, len(messages))
771-
for i, msg := range messages {
772-
if msgMap, ok := msg.(map[string]interface{}); ok {
773-
messageObj := MessageObject{}
774-
775-
// New structured fields (optional)
776-
if mt, ok := msgMap["type"].(string); ok {
777-
messageObj.Type = mt
778-
}
779-
if cb, ok := msgMap["content"].([]interface{}); ok {
780-
messageObj.Content = cb
781-
}
782-
if st, ok := msgMap["subtype"].(string); ok {
783-
messageObj.Subtype = st
784-
}
785-
if data, ok := msgMap["data"].(map[string]interface{}); ok {
786-
messageObj.Data = data
787-
}
788-
789-
// Assistant fields
790-
if model, ok := msgMap["model"].(string); ok {
791-
messageObj.Model = model
792-
}
793-
794-
// Result fields
795-
if dms, ok := msgMap["duration_ms"].(float64); ok {
796-
messageObj.DurationMs = int(dms)
797-
}
798-
if dams, ok := msgMap["duration_api_ms"].(float64); ok {
799-
messageObj.DurationApiMs = int(dams)
800-
}
801-
if isErr, ok := msgMap["is_error"].(bool); ok {
802-
messageObj.IsError = isErr
803-
}
804-
if nt, ok := msgMap["num_turns"].(float64); ok {
805-
messageObj.NumTurns = int(nt)
806-
}
807-
if sid, ok := msgMap["session_id"].(string); ok {
808-
messageObj.SessionID = sid
809-
}
810-
if tcu, ok := msgMap["total_cost_usd"].(float64); ok {
811-
messageObj.TotalCostUSD = &tcu
812-
}
813-
if usage, ok := msgMap["usage"].(map[string]interface{}); ok {
814-
messageObj.Usage = usage
815-
}
816-
if res, ok := msgMap["result"].(string); ok {
817-
messageObj.Result = &res
818-
}
819-
820-
// If content is a string (legacy runner), still store it (overwrites any array set above)
821-
if content, ok := msgMap["content"].(string); ok {
822-
messageObj.Content = content
823-
}
824-
825-
// legacy tool_* fields no longer supported
826-
827-
result.Messages[i] = messageObj
828-
}
829-
}
830-
}
831-
832754
// New: result summary fields (top-level in status)
833755
if st, ok := status["subtype"].(string); ok {
834756
result.Subtype = st
835757
}
836-
if dms, ok := status["duration_ms"].(float64); ok {
837-
result.DurationMs = int(dms)
838-
}
839-
if dams, ok := status["duration_api_ms"].(float64); ok {
840-
result.DurationApiMs = int(dams)
841-
}
758+
842759
if ie, ok := status["is_error"].(bool); ok {
843760
result.IsError = ie
844761
}
@@ -861,12 +778,6 @@ func parseStatus(status map[string]interface{}) *AgenticSessionStatus {
861778
if stateDir, ok := status["stateDir"].(string); ok {
862779
result.StateDir = stateDir
863780
}
864-
if ac, ok := status["artifactsCount"].(float64); ok {
865-
result.ArtifactsCount = int(ac)
866-
}
867-
if mc, ok := status["messagesCount"].(float64); ok {
868-
result.MessagesCount = int(mc)
869-
}
870781

871782
return result
872783
}

components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ export default function ProjectSessionDetailPage({ params }: { params: Promise<{
546546
)}
547547

548548
{/* Agentic Results */}
549-
{session.status?.message && (
549+
{session.status?.result && (
550550
<Card>
551551
<CardHeader>
552552
<CardTitle>Agentic Results</CardTitle>
@@ -555,7 +555,7 @@ export default function ProjectSessionDetailPage({ params }: { params: Promise<{
555555
<CardContent>
556556
<div className="bg-white rounded-lg border p-6 prose prose-sm max-w-none prose-headings:text-gray-900 prose-p:text-gray-700 prose-strong:text-gray-900 prose-code:bg-gray-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-pre:bg-gray-900 prose-pre:text-gray-100">
557557
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeHighlight]} components={outputComponents}>
558-
{session.status.finalOutput}
558+
{session.status.result}
559559
</ReactMarkdown>
560560
</div>
561561
</CardContent>

components/frontend/src/types/agentic-session.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ export type AgenticSessionStatus = {
112112
startTime?: string;
113113
completionTime?: string;
114114
jobName?: string;
115-
finalOutput?: string;
116115
cost?: number;
117116
// Storage & counts (align with CRD)
118117
stateDir?: string;

components/manifests/crds/agenticsessions-crd.yaml

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,6 @@ spec:
109109
message:
110110
type: string
111111
description: "Status message or error details"
112-
finalOutput:
113-
type: string
114-
description: "Final textual output for the session"
115112
startTime:
116113
type: string
117114
format: date-time
@@ -121,28 +118,13 @@ spec:
121118
jobName:
122119
type: string
123120
description: "Name of the Kubernetes job created for this session"
124-
cost:
125-
type: number
126-
description: "Total cost of the agentic session in USD"
127121
stateDir:
128122
type: string
129123
description: "Directory path where session state files are stored"
130-
artifactsCount:
131-
type: integer
132-
description: "Number of artifacts (screenshots, files) generated"
133-
messagesCount:
134-
type: integer
135-
description: "Number of messages in the conversation"
136124
# Result summary fields from the runner's ResultMessage
137125
subtype:
138126
type: string
139127
description: "Result subtype (e.g., success, error, interrupted)"
140-
duration_ms:
141-
type: integer
142-
description: "Total duration of the run in milliseconds"
143-
duration_api_ms:
144-
type: integer
145-
description: "Total API time in milliseconds"
146128
is_error:
147129
type: boolean
148130
description: "Whether the run ended with an error"

components/runners/claude-code-runner/auth_handler.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,12 @@ async def update_session_status(self, session_name: str, status_data: Dict[str,
187187
pass
188188

189189
# Filter allowed fields only
190-
allowed = {"phase", "completionTime", "cost", "finalOutput", "message"}
191-
filtered = {k: v for k, v in status_data.items() if k in allowed}
192-
193190
try:
194191
async with aiohttp.ClientSession() as session:
195192
async with session.put(
196193
endpoint,
197194
headers=headers,
198-
data=json.dumps(filtered)
195+
data=json.dumps(status_data)
199196
) as response:
200197
if response.status == 200:
201198
logger.info(f"Successfully updated session status")

0 commit comments

Comments
 (0)