diff --git a/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap b/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap index f858561b64f..2ff5da1bd86 100644 --- a/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +++ b/assistant/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap @@ -2499,3 +2499,13 @@ exports[`IPC message snapshots ServerMessage types tool_names_list_response seri "type": "tool_names_list_response", } `; + +exports[`IPC message snapshots ClientMessage types cu_session_finalized serializes to expected JSON 1`] = ` +{ + "sessionId": "cu-sess-001", + "status": "completed", + "stepCount": 5, + "summary": "Task completed successfully", + "type": "cu_session_finalized", +} +`; diff --git a/assistant/src/__tests__/ipc-snapshot.test.ts b/assistant/src/__tests__/ipc-snapshot.test.ts index 12c66cbd4bd..090bbc708dc 100644 --- a/assistant/src/__tests__/ipc-snapshot.test.ts +++ b/assistant/src/__tests__/ipc-snapshot.test.ts @@ -104,6 +104,13 @@ const clientMessages: Record = { type: 'cu_session_abort', sessionId: 'cu-sess-001', }, + cu_session_finalized: { + type: 'cu_session_finalized', + sessionId: 'cu-sess-001', + status: 'completed', + summary: 'Task completed successfully', + stepCount: 5, + }, cu_observation: { type: 'cu_observation', sessionId: 'cu-sess-001', diff --git a/assistant/src/daemon/handlers/computer-use.ts b/assistant/src/daemon/handlers/computer-use.ts index 0567047da6e..c8da4d2fd58 100644 --- a/assistant/src/daemon/handlers/computer-use.ts +++ b/assistant/src/daemon/handlers/computer-use.ts @@ -7,6 +7,7 @@ import { readBlob, deleteBlob, validateBlobKindEncoding } from '../ipc-blob-stor import type { CuSessionCreate, CuSessionAbort, + CuSessionFinalized, CuObservation, ServerMessage, } from '../ipc-protocol.js'; @@ -180,8 +181,27 @@ export async function handleCuObservation( }); } +export function handleCuSessionFinalized( + msg: CuSessionFinalized, + _socket: net.Socket, + _ctx: HandlerContext, +): void { + log.info( + { + sessionId: msg.sessionId, + status: msg.status, + stepCount: msg.stepCount, + hasRecording: !!msg.recording, + recordingSizeBytes: msg.recording?.sizeBytes, + recordingDurationMs: msg.recording?.durationMs, + }, + 'CU session finalized by client', + ); +} + export const computerUseHandlers = defineHandlers({ cu_session_create: handleCuSessionCreate, cu_session_abort: handleCuSessionAbort, + cu_session_finalized: handleCuSessionFinalized, cu_observation: handleCuObservation, }); diff --git a/assistant/src/daemon/ipc-contract-inventory.json b/assistant/src/daemon/ipc-contract-inventory.json index 1e0e4765593..3919340003e 100644 --- a/assistant/src/daemon/ipc-contract-inventory.json +++ b/assistant/src/daemon/ipc-contract-inventory.json @@ -23,6 +23,7 @@ "CuObservation", "CuSessionAbort", "CuSessionCreate", + "CuSessionFinalized", "DeleteQueuedMessage", "DiagnosticsExportRequest", "DocumentListRequest", @@ -272,6 +273,7 @@ "cu_observation", "cu_session_abort", "cu_session_create", + "cu_session_finalized", "delete_queued_message", "diagnostics_export_request", "document_list", diff --git a/assistant/src/daemon/ipc-contract.ts b/assistant/src/daemon/ipc-contract.ts index c449de86855..836e9c1c316 100644 --- a/assistant/src/daemon/ipc-contract.ts +++ b/assistant/src/daemon/ipc-contract.ts @@ -169,6 +169,10 @@ export interface CuSessionCreate { screenHeight: number; attachments?: UserMessageAttachment[]; interactionType?: 'computer_use' | 'text_qa'; + /** Origin chat session for result injection (QA workflow). */ + reportToSessionId?: string; + /** Marks this CU run as a QA/test workflow. */ + qaMode?: boolean; } export interface CuSessionAbort { @@ -176,6 +180,26 @@ export interface CuSessionAbort { sessionId: string; } +export interface CuSessionFinalized { + type: 'cu_session_finalized'; + sessionId: string; + status: 'completed' | 'responded' | 'failed' | 'cancelled'; + summary: string; + stepCount: number; + recording?: { + localPath: string; + mimeType: 'video/mp4'; + sizeBytes: number; + durationMs: number; + width: number; + height: number; + captureScope: 'window' | 'display'; + includeAudio: boolean; + targetBundleId?: string; + expiresAt?: number; + }; +} + export interface CuObservation { type: 'cu_observation'; sessionId: string; @@ -1028,6 +1052,7 @@ export type ClientMessage = | SandboxSetRequest | CuSessionCreate | CuSessionAbort + | CuSessionFinalized | CuObservation | RideShotgunStart | RideShotgunStop diff --git a/clients/shared/IPC/Generated/IPCContractGenerated.swift b/clients/shared/IPC/Generated/IPCContractGenerated.swift index 5dc945b9958..ce61b4d21e8 100644 --- a/clients/shared/IPC/Generated/IPCContractGenerated.swift +++ b/clients/shared/IPC/Generated/IPCContractGenerated.swift @@ -476,6 +476,32 @@ public struct IPCCuSessionCreate: Codable, Sendable { public let screenHeight: Int public let attachments: [IPCUserMessageAttachment]? public let interactionType: String? + /// Origin chat session for result injection (QA workflow). + public let reportToSessionId: String? + /// Marks this CU run as a QA/test workflow. + public let qaMode: Bool? +} + +public struct IPCCuSessionFinalized: Codable, Sendable { + public let type: String + public let sessionId: String + public let status: String + public let summary: String + public let stepCount: Int + public let recording: IPCCuSessionFinalizedRecording? +} + +public struct IPCCuSessionFinalizedRecording: Codable, Sendable { + public let localPath: String + public let mimeType: String + public let sizeBytes: Int + public let durationMs: Double + public let width: Int + public let height: Int + public let captureScope: String + public let includeAudio: Bool + public let targetBundleId: String? + public let expiresAt: Int? } public struct IPCDaemonStatusMessage: Codable, Sendable {