From 2982446fa01732da58bd5483f203de68e5d57524 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Tue, 23 Jun 2026 13:38:25 +0800 Subject: [PATCH 1/8] fix(gateway): stabilize terminal stream over HTTP/2 --- crates/agent-gateway/internal/server/grpc.go | 11 ++ .../internal/server/grpc_test.go | 72 +++++++++++ .../src-tauri/src/services/gateway.rs | 118 ++++++++++++++++-- 3 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 crates/agent-gateway/internal/server/grpc_test.go diff --git a/crates/agent-gateway/internal/server/grpc.go b/crates/agent-gateway/internal/server/grpc.go index 542a1593..5624f67f 100644 --- a/crates/agent-gateway/internal/server/grpc.go +++ b/crates/agent-gateway/internal/server/grpc.go @@ -136,6 +136,10 @@ func (s *GRPCServer) AgentTerminalConnect(stream gatewayv1.AgentGateway_AgentTer ctx, cancel := context.WithCancel(stream.Context()) defer cancel() + if err := stream.Send(gatewayTerminalStreamReadyFrame()); err != nil { + return err + } + sendErrCh := make(chan error, 1) recvErrCh := make(chan error, 1) go func() { @@ -193,6 +197,13 @@ func (s *GRPCServer) AgentTerminalConnect(stream gatewayv1.AgentGateway_AgentTer } } +func gatewayTerminalStreamReadyFrame() *gatewayv1.TerminalStreamFrame { + return &gatewayv1.TerminalStreamFrame{ + Kind: "detach", + StreamId: "gateway-ready-" + uuid.NewString(), + } +} + func (s *GRPCServer) heartbeatLoop(ctx context.Context, sess *session.AgentSession) { period := s.heartbeatPeriod() ticker := time.NewTicker(period) diff --git a/crates/agent-gateway/internal/server/grpc_test.go b/crates/agent-gateway/internal/server/grpc_test.go new file mode 100644 index 00000000..e8020849 --- /dev/null +++ b/crates/agent-gateway/internal/server/grpc_test.go @@ -0,0 +1,72 @@ +package server + +import ( + "context" + "net" + "testing" + "time" + + "github.com/liveagent/agent-gateway/internal/config" + gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" + "github.com/liveagent/agent-gateway/internal/session" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" +) + +func TestAgentTerminalConnectSendsReadyFrame(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + grpcServer := grpc.NewServer() + gatewayv1.RegisterAgentGatewayServer(grpcServer, NewGRPCServer(&config.Config{}, session.NewManager())) + t.Cleanup(func() { + grpcServer.Stop() + _ = listener.Close() + }) + + serveErr := make(chan error, 1) + go func() { + serveErr <- grpcServer.Serve(listener) + }() + + conn, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return listener.DialContext(ctx) + }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + t.Fatalf("dial bufconn gRPC: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + }) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + stream, err := gatewayv1.NewAgentGatewayClient(conn).AgentTerminalConnect(ctx) + if err != nil { + t.Fatalf("open terminal stream: %v", err) + } + frame, err := stream.Recv() + if err != nil { + t.Fatalf("receive terminal ready frame: %v", err) + } + if frame.GetKind() != "detach" { + t.Fatalf("ready frame kind = %q, want detach", frame.GetKind()) + } + if streamID := frame.GetStreamId(); len(streamID) < len("gateway-ready-") || streamID[:len("gateway-ready-")] != "gateway-ready-" { + t.Fatalf("ready frame stream id = %q, want gateway-ready-*", streamID) + } + + grpcServer.Stop() + select { + case err := <-serveErr: + if err != nil && err != grpc.ErrServerStopped { + t.Fatalf("Serve returned error: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("gRPC server did not stop") + } +} diff --git a/crates/agent-gui/src-tauri/src/services/gateway.rs b/crates/agent-gui/src-tauri/src/services/gateway.rs index 3019a589..32e08dc4 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway.rs @@ -59,6 +59,7 @@ const GATEWAY_RECONNECT_DELAY: Duration = Duration::from_secs(5); const GATEWAY_TERMINAL_STREAM_RECONNECT_MIN: Duration = Duration::from_millis(250); const GATEWAY_TERMINAL_STREAM_RECONNECT_MAX: Duration = Duration::from_secs(5); const GATEWAY_TERMINAL_STREAM_STABLE_AFTER: Duration = Duration::from_secs(30); +const GATEWAY_TERMINAL_STREAM_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(5); const GATEWAY_CHAT_LEASE_MS: u64 = 15_000; const GATEWAY_CHAT_RUNNING_LEASE_MS: u64 = 30 * 60_000; const GATEWAY_CHAT_LEASE_SWEEP_INTERVAL: Duration = Duration::from_secs(5); @@ -916,6 +917,7 @@ impl GatewayController { let (terminal_tx, terminal_rx) = mpsc::channel::(4096); let result = async { + queue_terminal_stream_handshake_frame(&terminal_tx)?; let mut request = tonic::Request::new(ReceiverStream::new(terminal_rx)); insert_bearer_metadata(request.metadata_mut(), &config.token)?; let response = tokio::select! { @@ -926,11 +928,16 @@ impl GatewayController { return Ok(()); } response = client.agent_terminal_connect(request) => { - response.map_err(|error| format!("open gateway terminal stream failed: {error}"))? + response.map_err(|error| { + format_gateway_terminal_stream_rpc_error("open", &error, &config) + })? } }; self.set_terminal_stream_sender(Some(terminal_tx.clone())); let mut inbound = response.into_inner(); + let mut keepalive = tokio::time::interval(GATEWAY_TERMINAL_STREAM_KEEPALIVE_INTERVAL); + keepalive.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + keepalive.tick().await; loop { tokio::select! { changed = stop_rx.changed() => { @@ -938,6 +945,9 @@ impl GatewayController { return Ok(()); } } + _ = keepalive.tick() => { + queue_terminal_stream_keepalive_frame(&terminal_tx).await?; + } message = inbound.message() => { match message { Ok(Some(frame)) => { @@ -947,7 +957,7 @@ impl GatewayController { } Ok(None) => return Ok(()), Err(error) => { - return Err(format!("gateway terminal stream receive failed: {error}")) + return Err(format_gateway_terminal_stream_rpc_error("receive", &error, &config)) } } } @@ -4795,11 +4805,12 @@ mod tests { use super::{ build_chat_event_envelope, build_endpoint, build_grpc_url, build_local_settings_update_event_payload, build_tunnel_upstream_url, - history_share_resolve_error_code, merge_settings_sync_snapshot, normalize_tunnel_ttl, - proto, required_terminal_project_path_key, set_disconnected_status, tunnel_expires_at, - validate_tunnel_target_url, GatewayChatRequestEvent, GatewayController, - GatewayStatusSnapshot, RemoteChatInboxRecord, GATEWAY_CHAT_LEASE_MS, - GATEWAY_CHAT_RUNNING_LEASE_MS, + format_gateway_terminal_stream_rpc_error, history_share_resolve_error_code, + merge_settings_sync_snapshot, normalize_tunnel_ttl, proto, + queue_terminal_stream_handshake_frame, required_terminal_project_path_key, + set_disconnected_status, tunnel_expires_at, validate_tunnel_target_url, + GatewayChatRequestEvent, GatewayController, GatewayStatusSnapshot, RemoteChatInboxRecord, + GATEWAY_CHAT_LEASE_MS, GATEWAY_CHAT_RUNNING_LEASE_MS, }; use crate::commands::settings::RemoteSettingsPayload; use serde_json::{json, Value}; @@ -5225,6 +5236,48 @@ mod tests { assert_eq!(grpc_url, "http://tcp.proxy.rlwy.net:12345"); } + #[test] + fn terminal_stream_handshake_frame_is_gateway_noop() { + let (sender, mut receiver) = tokio::sync::mpsc::channel::(1); + + queue_terminal_stream_handshake_frame(&sender).expect("queue terminal stream handshake"); + + let frame = receiver + .try_recv() + .expect("terminal stream handshake frame"); + assert_eq!(frame.kind, "detach"); + assert!(frame.stream_id.starts_with("desktop-handshake-")); + assert!(frame.session_id.is_empty()); + } + + #[test] + fn terminal_stream_h2_error_points_to_grpc_endpoint() { + let config = RemoteSettingsPayload { + enabled: true, + gateway_url: "https://gateway.example.com".to_string(), + grpc_port: 443, + grpc_endpoint: String::new(), + token: "dev-token".to_string(), + agent_id: "agent".to_string(), + auto_reconnect: true, + heartbeat_interval: 30, + enable_web_terminal: true, + enable_web_ssh_terminal: true, + enable_web_git: false, + enable_web_tunnels: false, + }; + + let message = format_gateway_terminal_stream_rpc_error( + "receive", + &tonic::Status::internal("h2 protocol error: http2 error"), + &config, + ); + + assert!(message.contains("receive failed")); + assert!(message.contains("HTTP/2 bidi streams")); + assert!(message.contains("https://gateway.example.com")); + } + #[test] fn build_chat_event_envelope_preserves_tool_result_arguments() { let envelope = build_chat_event_envelope( @@ -5569,6 +5622,57 @@ fn build_grpc_url(config: &RemoteSettingsPayload) -> Result { Ok(url.to_string().trim_end_matches('/').to_string()) } +fn queue_terminal_stream_handshake_frame( + sender: &mpsc::Sender, +) -> Result<(), String> { + // Some HTTP/2 proxies do not fully establish a bidi stream until the client + // sends its first DATA frame. `detach` is a gateway no-op and is not forwarded + // to browser terminal subscribers. + sender + .try_send(terminal_stream_noop_frame("desktop-handshake")) + .map_err(|error| format!("queue gateway terminal stream handshake failed: {error}")) +} + +async fn queue_terminal_stream_keepalive_frame( + sender: &mpsc::Sender, +) -> Result<(), String> { + sender + .send(terminal_stream_noop_frame("desktop-keepalive")) + .await + .map_err(|error| format!("queue gateway terminal stream keepalive failed: {error}")) +} + +fn terminal_stream_noop_frame(prefix: &str) -> proto::TerminalStreamFrame { + proto::TerminalStreamFrame { + kind: "detach".to_string(), + stream_id: format!("{}-{}", prefix.trim(), Uuid::new_v4()), + ..Default::default() + } +} + +fn format_gateway_terminal_stream_rpc_error( + phase: &str, + error: &tonic::Status, + config: &RemoteSettingsPayload, +) -> String { + let message = error.to_string(); + if !is_h2_protocol_error(&message) { + return format!("gateway terminal stream {phase} failed: {message}"); + } + + let endpoint = build_grpc_url(config).unwrap_or_else(|_| "invalid endpoint".to_string()); + format!( + "gateway terminal stream {phase} failed: {message}. \ + The gateway terminal stream requires a gRPC endpoint that supports HTTP/2 bidi streams; \ + check Remote gRPC Endpoint / gRPC port. Current endpoint: {endpoint}" + ) +} + +fn is_h2_protocol_error(message: &str) -> bool { + let normalized = message.to_ascii_lowercase(); + normalized.contains("h2 protocol error") || normalized.contains("http2 error") +} + fn build_endpoint(grpc_url: &str) -> Result { let endpoint = Endpoint::from_shared(grpc_url.to_string()) .map_err(|e| format!("invalid gateway endpoint: {e}"))? From f8936668c4d136fa941a8f4a1327156190cc0f6e Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Tue, 23 Jun 2026 14:15:14 +0800 Subject: [PATCH 2/8] fix(gateway): sync webui with gui live chat streams --- .../internal/server/websocket.go | 2 +- .../internal/server/websocket_payload_test.go | 26 ++++ .../internal/server/websocket_payloads.go | 32 +++- .../internal/session/manager_chat_runs.go | 140 +++++++++++++++++- .../internal/session/manager_history_sync.go | 9 +- .../session/sqlite_chat_event_store.go | 50 ++++++- .../test/session/chat_event_store_test.go | 100 +++++++++++++ .../test/session/manager_test.go | 75 ++++++++++ .../test/webui/gateway-socket-client.test.mjs | 6 +- .../agent-gateway/web/src/app/GatewayApp.tsx | 7 +- .../web/src/lib/gatewaySocket.ts | 5 + .../agent-gateway/web/src/lib/gatewayTypes.ts | 5 + 12 files changed, 449 insertions(+), 8 deletions(-) diff --git a/crates/agent-gateway/internal/server/websocket.go b/crates/agent-gateway/internal/server/websocket.go index 4495928c..02d49cec 100644 --- a/crates/agent-gateway/internal/server/websocket.go +++ b/crates/agent-gateway/internal/server/websocket.go @@ -258,7 +258,7 @@ func (c *websocketConnection) startHistorySyncForwarder() { if !ok { return } - if err := c.writeHistoryEvent(websocketHistorySyncPayload(event)); err != nil { + if err := c.writeHistoryEvent(websocketHistorySyncPayload(event, c.sm.ActiveChatRunSummaries()...)); err != nil { c.close() return } diff --git a/crates/agent-gateway/internal/server/websocket_payload_test.go b/crates/agent-gateway/internal/server/websocket_payload_test.go index 3f3649d0..e59ea688 100644 --- a/crates/agent-gateway/internal/server/websocket_payload_test.go +++ b/crates/agent-gateway/internal/server/websocket_payload_test.go @@ -82,6 +82,32 @@ func TestActiveChatRunSummaryPayloadIncludesReplayCursor(t *testing.T) { } } +func TestHistoryRunningPayloadIncludesReplayCursor(t *testing.T) { + payload := websocketHistorySyncPayload(&gatewayv1.HistorySyncEvent{ + Kind: "running", + ConversationId: "conversation-1", + Conversation: &gatewayv1.ConversationSummary{ + Id: "conversation-1", + Cwd: "/workspace", + }, + }, session.ActiveChatRunSummary{ + ConversationID: "conversation-1", + RequestID: "conversation-live-conversation-1", + FirstSeq: 2, + LatestSeq: 1, + RunEpoch: 5, + UpdatedAt: 123, + }) + + if payload["run_id"] != "conversation-live-conversation-1" || + payload["first_seq"] != int64(2) || + payload["latest_seq"] != int64(1) || + payload["run_epoch"] != int64(5) || + payload["updated_at"] != int64(123) { + t.Fatalf("history running payload = %#v", payload) + } +} + func TestWebsocketTerminalPayloadsPreserveOutputOffsets(t *testing.T) { response := websocketTerminalResponsePayload(&gatewayv1.TerminalResponse{ Action: "attach", diff --git a/crates/agent-gateway/internal/server/websocket_payloads.go b/crates/agent-gateway/internal/server/websocket_payloads.go index 70e46f92..9d6c177f 100644 --- a/crates/agent-gateway/internal/server/websocket_payloads.go +++ b/crates/agent-gateway/internal/server/websocket_payloads.go @@ -146,7 +146,10 @@ func websocketHistoryShareStatusPayload(share *gatewayv1.HistoryShareStatus) map return websocketProtoPayload(share, true) } -func websocketHistorySyncPayload(event *gatewayv1.HistorySyncEvent) map[string]any { +func websocketHistorySyncPayload( + event *gatewayv1.HistorySyncEvent, + activeRuns ...session.ActiveChatRunSummary, +) map[string]any { payload := map[string]any{ "kind": strings.TrimSpace(event.GetKind()), "conversation_id": strings.TrimSpace(event.GetConversationId()), @@ -155,6 +158,33 @@ func websocketHistorySyncPayload(event *gatewayv1.HistorySyncEvent) map[string]a if conversation := event.GetConversation(); conversation != nil { payload["conversation"] = websocketConversationSummaryPayload(conversation) } + if payload["kind"] == "running" { + conversationID := strings.TrimSpace(event.GetConversationId()) + if conversationID == "" && event.GetConversation() != nil { + conversationID = strings.TrimSpace(event.GetConversation().GetId()) + } + for _, summary := range activeRuns { + if strings.TrimSpace(summary.ConversationID) != conversationID { + continue + } + if requestID := strings.TrimSpace(summary.RequestID); requestID != "" { + payload["run_id"] = requestID + } + if summary.FirstSeq > 0 { + payload["first_seq"] = summary.FirstSeq + } + if summary.LatestSeq > 0 { + payload["latest_seq"] = summary.LatestSeq + } + if summary.RunEpoch > 0 { + payload["run_epoch"] = summary.RunEpoch + } + if summary.UpdatedAt > 0 { + payload["updated_at"] = summary.UpdatedAt + } + break + } + } return payload } diff --git a/crates/agent-gateway/internal/session/manager_chat_runs.go b/crates/agent-gateway/internal/session/manager_chat_runs.go index a3d49342..7b6fc353 100644 --- a/crates/agent-gateway/internal/session/manager_chat_runs.go +++ b/crates/agent-gateway/internal/session/manager_chat_runs.go @@ -21,6 +21,140 @@ func (m *Manager) StartPendingChatCommandRun( return m.startPendingChatCommandRun(requestID, conversationID, clientRequestID, workdirInput...) } +func conversationLiveChatRunID(conversationID string) string { + return "conversation-live-" + strings.TrimSpace(conversationID) +} + +func (m *Manager) ensureConversationChatRun( + conversationID string, + workdir string, + now time.Time, +) (ChatRunSnapshot, bool, error) { + conversationID = strings.TrimSpace(conversationID) + if conversationID == "" { + return ChatRunSnapshot{}, false, ErrChatRunNotFound + } + workdir = strings.TrimSpace(workdir) + if now.IsZero() { + now = time.Now() + } + + requestID := conversationLiveChatRunID(conversationID) + sessionEpoch := m.currentSessionEpoch() + + m.chatStore.chatMu.Lock() + m.pruneExpiredChatRunsLocked(now) + if existingRequestID := strings.TrimSpace(m.chatStore.chatRunByConversation[conversationID]); existingRequestID != "" { + if run := m.chatStore.chatRuns[existingRequestID]; run != nil && !run.done { + if workdir != "" { + run.workdir = workdir + } + run.applyState(ChatRunStateRunning) + run.updatedAt = now + snapshot := run.snapshot() + m.chatStore.chatMu.Unlock() + return snapshot, false, nil + } + } + if run := m.chatStore.chatRuns[requestID]; run != nil && !run.done { + if workdir != "" { + run.workdir = workdir + } + run.conversationID = conversationID + m.chatStore.chatRunByConversation[conversationID] = requestID + run.applyState(ChatRunStateRunning) + run.updatedAt = now + snapshot := run.snapshot() + m.chatStore.chatMu.Unlock() + return snapshot, false, nil + } + m.chatStore.chatMu.Unlock() + + if store := m.chatStore.eventStore; store != nil { + snapshot, created, err := store.StartRun(ChatRunStoreStart{ + RequestID: requestID, + ConversationID: conversationID, + Workdir: workdir, + State: ChatRunStateRunning, + CreatedAt: now, + }) + if err != nil { + return ChatRunSnapshot{}, false, err + } + + m.chatStore.chatMu.Lock() + defer m.chatStore.chatMu.Unlock() + m.pruneExpiredChatRunsLocked(now) + if liveRequestID := strings.TrimSpace(m.chatStore.chatRunByConversation[conversationID]); liveRequestID != "" && liveRequestID != requestID { + if liveRun := m.chatStore.chatRuns[liveRequestID]; liveRun != nil && !liveRun.done { + if workdir != "" { + liveRun.workdir = workdir + } + liveRun.applyState(ChatRunStateRunning) + liveRun.updatedAt = now + return liveRun.snapshot(), false, nil + } + } + if created { + if latestSeq := m.latestConversationSeqLocked(conversationID); latestSeq > snapshot.LatestSeq { + snapshot.LatestSeq = latestSeq + } + } + reopenedDoneRun := false + if existing := m.chatStore.chatRuns[requestID]; existing != nil && existing.done { + reopenedDoneRun = true + } + run := m.upsertChatRunSnapshotLocked(snapshot, sessionEpoch, now) + if run == nil { + return snapshot, created, nil + } + if reopenedDoneRun { + run.events = nil + } + if workdir != "" { + run.workdir = workdir + } + run.conversationID = conversationID + m.chatStore.chatRunByConversation[conversationID] = requestID + run.applyState(ChatRunStateRunning) + run.updatedAt = now + return run.snapshot(), created, nil + } + + m.chatStore.chatMu.Lock() + defer m.chatStore.chatMu.Unlock() + m.pruneExpiredChatRunsLocked(now) + if liveRequestID := strings.TrimSpace(m.chatStore.chatRunByConversation[conversationID]); liveRequestID != "" && liveRequestID != requestID { + if liveRun := m.chatStore.chatRuns[liveRequestID]; liveRun != nil && !liveRun.done { + if workdir != "" { + liveRun.workdir = workdir + } + liveRun.applyState(ChatRunStateRunning) + liveRun.updatedAt = now + return liveRun.snapshot(), false, nil + } + } + if existing := m.chatStore.chatRuns[requestID]; existing != nil { + m.removeChatRunLocked(requestID, existing) + } + m.chatStore.nextChatRunEpoch += 1 + run := &chatRun{ + requestID: requestID, + conversationID: conversationID, + workdir: workdir, + sessionEpoch: sessionEpoch, + runEpoch: m.chatStore.nextChatRunEpoch, + state: ChatRunStateRunning, + nextSeq: m.latestConversationSeqLocked(conversationID), + updatedAt: now, + subscribers: make(map[int]*chatRunSubscriber), + } + run.applyState(ChatRunStateRunning) + m.chatStore.chatRuns[requestID] = run + m.chatStore.chatRunByConversation[conversationID] = requestID + return run.snapshot(), true, nil +} + func (m *Manager) StartAcceptedChatCommandRun( requestID string, conversationID string, @@ -322,11 +456,15 @@ func (m *Manager) ActiveChatRunSummaries() []ActiveChatRunSummary { if conversationID == "" { continue } + firstSeq := run.snapshot().FirstSeq + if firstSeq <= 0 { + firstSeq = run.nextSeq + 1 + } summary := ActiveChatRunSummary{ ConversationID: conversationID, RequestID: strings.TrimSpace(run.requestID), Workdir: strings.TrimSpace(run.workdir), - FirstSeq: run.snapshot().FirstSeq, + FirstSeq: firstSeq, LatestSeq: run.nextSeq, RunEpoch: run.runEpoch, UpdatedAt: run.updatedAt.UnixMilli(), diff --git a/crates/agent-gateway/internal/session/manager_history_sync.go b/crates/agent-gateway/internal/session/manager_history_sync.go index 4077e922..ecad46d6 100644 --- a/crates/agent-gateway/internal/session/manager_history_sync.go +++ b/crates/agent-gateway/internal/session/manager_history_sync.go @@ -1,6 +1,7 @@ package session import ( + "log" "strings" "time" @@ -78,7 +79,6 @@ func (m *Manager) updateActiveHistoryRun(event *gatewayv1.HistorySyncEvent) { now := time.Now() m.chatStore.chatMu.Lock() - defer m.chatStore.chatMu.Unlock() m.pruneExpiredChatRunsLocked(now) switch kind { @@ -97,10 +97,16 @@ func (m *Manager) updateActiveHistoryRun(event *gatewayv1.HistorySyncEvent) { run.workdir = workdir } } + m.chatStore.chatMu.Unlock() + if _, _, err := m.ensureConversationChatRun(conversationID, workdir, now); err != nil { + log.Printf("ensure conversation chat run failed conversation_id=%q: %v", conversationID, err) + } + return case "idle", "delete": delete(m.chatStore.historyActiveRuns, conversationID) case "upsert": if workdir == "" { + m.chatStore.chatMu.Unlock() return } if existing, ok := m.chatStore.historyActiveRuns[conversationID]; ok { @@ -114,6 +120,7 @@ func (m *Manager) updateActiveHistoryRun(event *gatewayv1.HistorySyncEvent) { } } } + m.chatStore.chatMu.Unlock() } func (m *Manager) releaseCompletedChatRunAfterHistoryUpsert(event *gatewayv1.HistorySyncEvent) { diff --git a/crates/agent-gateway/internal/session/sqlite_chat_event_store.go b/crates/agent-gateway/internal/session/sqlite_chat_event_store.go index 822777bb..90210b0f 100644 --- a/crates/agent-gateway/internal/session/sqlite_chat_event_store.go +++ b/crates/agent-gateway/internal/session/sqlite_chat_event_store.go @@ -28,6 +28,7 @@ type ChatRunStoreStart struct { ConversationID string ClientRequestID string Workdir string + State string CreatedAt time.Time } @@ -153,6 +154,10 @@ func (s *sqliteChatEventStore) StartRun(input ChatRunStoreStart) (ChatRunSnapsho input.ConversationID = strings.TrimSpace(input.ConversationID) input.ClientRequestID = strings.TrimSpace(input.ClientRequestID) input.Workdir = strings.TrimSpace(input.Workdir) + input.State = normalizeChatRunState(input.State) + if input.State == "" { + input.State = ChatRunStateQueued + } if input.RequestID == "" { return ChatRunSnapshot{}, false, ErrChatRunNotFound } @@ -187,6 +192,47 @@ func (s *sqliteChatEventStore) StartRun(input ChatRunStoreStart) (ChatRunSnapsho return ChatRunSnapshot{}, false, err } if ok { + if input.ClientRequestID == "" && snapshot.ClientRequestID == "" && snapshot.Done { + conversationID := input.ConversationID + if conversationID == "" { + conversationID = snapshot.ConversationID + } + workdir := input.Workdir + if workdir == "" { + workdir = snapshot.Workdir + } + runEpoch, err := nextChatRunEpochTx(ctx, tx) + if err != nil { + return ChatRunSnapshot{}, false, err + } + latestSeq, err := latestConversationSeqTx(ctx, tx, conversationID) + if err != nil { + return ChatRunSnapshot{}, false, err + } + if latestSeq < snapshot.LatestSeq { + latestSeq = snapshot.LatestSeq + } + if _, err := tx.ExecContext(ctx, ` + UPDATE chat_runs + SET conversation_id = ?, workdir = ?, run_epoch = ?, state = ?, + error_code = '', done = 0, latest_seq = max(latest_seq, ?), updated_at = ? + WHERE run_id = ? + `, conversationID, workdir, runEpoch, input.State, latestSeq, nowMs, input.RequestID); err != nil { + return ChatRunSnapshot{}, false, err + } + if err := tx.Commit(); err != nil { + return ChatRunSnapshot{}, false, err + } + return ChatRunSnapshot{ + RequestID: input.RequestID, + ConversationID: conversationID, + Workdir: workdir, + RunEpoch: runEpoch, + FirstSeq: snapshot.FirstSeq, + LatestSeq: latestSeq, + State: input.State, + }, true, nil + } if err := tx.Commit(); err != nil { return ChatRunSnapshot{}, false, err } @@ -206,7 +252,7 @@ func (s *sqliteChatEventStore) StartRun(input ChatRunStoreStart) (ChatRunSnapsho run_id, conversation_id, client_request_id, workdir, run_epoch, state, error_code, done, latest_seq, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, '', 0, ?, ?, ?) - `, input.RequestID, input.ConversationID, input.ClientRequestID, input.Workdir, runEpoch, ChatRunStateQueued, latestSeq, nowMs, nowMs); err != nil { + `, input.RequestID, input.ConversationID, input.ClientRequestID, input.Workdir, runEpoch, input.State, latestSeq, nowMs, nowMs); err != nil { return ChatRunSnapshot{}, false, err } if input.ClientRequestID != "" { @@ -227,7 +273,7 @@ func (s *sqliteChatEventStore) StartRun(input ChatRunStoreStart) (ChatRunSnapsho Workdir: input.Workdir, RunEpoch: runEpoch, LatestSeq: latestSeq, - State: ChatRunStateQueued, + State: input.State, }, true, nil } diff --git a/crates/agent-gateway/test/session/chat_event_store_test.go b/crates/agent-gateway/test/session/chat_event_store_test.go index ece0265e..8726d138 100644 --- a/crates/agent-gateway/test/session/chat_event_store_test.go +++ b/crates/agent-gateway/test/session/chat_event_store_test.go @@ -177,6 +177,106 @@ func TestSQLiteChatEventStoreReplaysCompletedRunAndDedupesCommand(t *testing.T) } } +func TestSQLiteHistoryRunningCreatesAndReopensAttachableConversationRun(t *testing.T) { + t.Parallel() + + dbPath := filepath.Join(t.TempDir(), "gateway-chat.sqlite3") + sm, store := newPersistentTestSessionManager(t, dbPath) + defer store.Close() + + dispatchRunning := func() { + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "history-sync-running", + Payload: &gatewayv1.AgentEnvelope_HistorySync{ + HistorySync: &gatewayv1.HistorySyncEvent{ + Kind: "running", + ConversationId: "conversation-1", + Conversation: &gatewayv1.ConversationSummary{ + Id: "conversation-1", + Cwd: "/workspace", + }, + }, + }, + }) + } + + dispatchRunning() + ch, done, cleanup, snapshot, err := sm.SubscribeChatRun("", "conversation-1", 0) + if err != nil { + t.Fatalf("SubscribeChatRun first running: %v", err) + } + if snapshot.RequestID != "conversation-live-conversation-1" || + snapshot.State != session.ChatRunStateRunning || + snapshot.Workdir != "/workspace" { + t.Fatalf("first running snapshot = %#v", snapshot) + } + assertDoneOpen(t, done) + select { + case event := <-ch: + t.Fatalf("unexpected first replay before token: %#v", event) + default: + } + cleanup() + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "conversation-live-conversation-1", + Payload: &gatewayv1.AgentEnvelope_ChatEvent{ + ChatEvent: &gatewayv1.ChatEvent{ + Type: gatewayv1.ChatEvent_DONE, + ConversationId: "conversation-1", + Data: `{}`, + }, + }, + }) + + dispatchRunning() + summaries := sm.ActiveChatRunSummaries() + if len(summaries) != 1 || + summaries[0].RequestID != "conversation-live-conversation-1" || + summaries[0].FirstSeq != 2 || + summaries[0].LatestSeq != 1 { + t.Fatalf("second active summaries = %#v", summaries) + } + + replayCh, replayDone, replayCleanup, replaySnapshot, err := sm.SubscribeChatRun( + "", + "conversation-1", + summaries[0].FirstSeq-1, + ) + if err != nil { + t.Fatalf("SubscribeChatRun second running: %v", err) + } + defer replayCleanup() + assertDoneOpen(t, replayDone) + if replaySnapshot.State != session.ChatRunStateRunning || replaySnapshot.LatestSeq != 1 { + t.Fatalf("second running snapshot = %#v", replaySnapshot) + } + select { + case event := <-replayCh: + t.Fatalf("unexpected replay from previous turn: %#v", event) + default: + } + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "conversation-live-conversation-1", + Payload: &gatewayv1.AgentEnvelope_ChatEvent{ + ChatEvent: &gatewayv1.ChatEvent{ + Type: gatewayv1.ChatEvent_TOKEN, + ConversationId: "conversation-1", + Data: `{"text":"next"}`, + }, + }, + }) + select { + case event := <-replayCh: + if event.Seq != 2 || event.Event.GetType() != gatewayv1.ChatEvent_TOKEN { + t.Fatalf("second live event = %#v, want token seq 2", event) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for second live token") + } +} + func TestSQLiteChatEventStoreDoesNotPersistToolCallDeltaPayloads(t *testing.T) { t.Parallel() diff --git a/crates/agent-gateway/test/session/manager_test.go b/crates/agent-gateway/test/session/manager_test.go index d96084c9..de7de2bf 100644 --- a/crates/agent-gateway/test/session/manager_test.go +++ b/crates/agent-gateway/test/session/manager_test.go @@ -991,6 +991,81 @@ func TestDesktopBroadcastChatEventCreatesAttachableRun(t *testing.T) { } } +func TestHistoryRunningCreatesAttachableConversationRun(t *testing.T) { + t.Parallel() + + sm := newTestSessionManager() + sm.SetSession(session.NewAgentSession(sm.LatestAuthSnapshot())) + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "history-sync-1", + Payload: &gatewayv1.AgentEnvelope_HistorySync{ + HistorySync: &gatewayv1.HistorySyncEvent{ + Kind: "running", + ConversationId: "conversation-1", + Conversation: &gatewayv1.ConversationSummary{ + Id: "conversation-1", + Cwd: "/workspace", + }, + }, + }, + }) + + summaries := sm.ActiveChatRunSummaries() + if len(summaries) != 1 || + summaries[0].ConversationID != "conversation-1" || + summaries[0].RequestID != "conversation-live-conversation-1" || + summaries[0].Workdir != "/workspace" || + summaries[0].FirstSeq != 1 { + t.Fatalf("active summaries = %#v", summaries) + } + + ch, done, cleanup, snapshot, err := sm.SubscribeChatRun("", "conversation-1", 0) + if err != nil { + t.Fatalf("SubscribeChatRun: %v", err) + } + defer cleanup() + assertDoneOpen(t, done) + if snapshot.RequestID != "conversation-live-conversation-1" || + snapshot.State != session.ChatRunStateRunning || + snapshot.Workdir != "/workspace" || + snapshot.Done { + t.Fatalf("snapshot = %#v", snapshot) + } + + select { + case event := <-ch: + t.Fatalf("unexpected replay before first token: %#v", event) + default: + } + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "conversation-live-conversation-1", + Payload: &gatewayv1.AgentEnvelope_ChatEvent{ + ChatEvent: &gatewayv1.ChatEvent{ + Type: gatewayv1.ChatEvent_TOKEN, + ConversationId: "conversation-1", + Data: `{"text":"hello"}`, + }, + }, + }) + + select { + case event := <-ch: + if event.Seq != 1 { + t.Fatalf("event seq = %d, want 1", event.Seq) + } + if event.Event.GetType() != gatewayv1.ChatEvent_TOKEN { + t.Fatalf("event type = %v, want TOKEN", event.Event.GetType()) + } + if event.Workdir != "/workspace" { + t.Fatalf("event workdir = %q, want /workspace", event.Workdir) + } + case <-time.After(time.Second): + t.Fatalf("timed out waiting for token after history running") + } +} + func TestCompletedHistoryUpsertDoesNotPreemptTerminalChatEvent(t *testing.T) { t.Parallel() diff --git a/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs b/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs index 16f3a213..a2c38510 100644 --- a/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs +++ b/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs @@ -504,12 +504,16 @@ test("GatewayWebSocketClient streamChatEvents sends replay cursor", async () => try { const client = getGatewayWebSocketClient(" token "); const events = []; - for await (const event of client.streamChatEvents(" conversation-1 ", { afterSeq: 41 })) { + for await (const event of client.streamChatEvents(" conversation-1 ", { + runId: " live-run ", + afterSeq: 41, + })) { events.push(event); } assert.equal(fetchCalls.length, 1); assert.equal(fetchCalls[0].url.pathname, "/api/chat/events"); + assert.equal(fetchCalls[0].url.searchParams.get("run_id"), "live-run"); assert.equal(fetchCalls[0].url.searchParams.get("conversation_id"), "conversation-1"); assert.equal(fetchCalls[0].url.searchParams.get("after_seq"), "41"); assert.equal(fetchCalls[0].init.method, "GET"); diff --git a/crates/agent-gateway/web/src/app/GatewayApp.tsx b/crates/agent-gateway/web/src/app/GatewayApp.tsx index ab0ceefe..fc7e6476 100644 --- a/crates/agent-gateway/web/src/app/GatewayApp.tsx +++ b/crates/agent-gateway/web/src/app/GatewayApp.tsx @@ -2241,6 +2241,7 @@ export default function GatewayApp() { let terminalEventSeen = false; try { for await (const event of currentApi.streamChatEvents(conversationIdValue, { + runId: runtime?.runId, afterSeq: streamAfterSeq, signal: controller.signal, })) { @@ -2368,8 +2369,12 @@ export default function GatewayApp() { if (event.kind === "running" || event.kind === "idle") { setRemoteConversationRunningState(targetConversationId, event.kind === "running", { + runId: event.run_id, workdir: event.conversation?.cwd, - updatedAt: event.conversation?.updated_at, + firstSeq: event.first_seq, + latestSeq: event.latest_seq, + runEpoch: event.run_epoch, + updatedAt: event.updated_at ?? event.conversation?.updated_at, }); if (event.kind === "running") { if (!isConversationEventStreamSubscribed(targetConversationId)) { diff --git a/crates/agent-gateway/web/src/lib/gatewaySocket.ts b/crates/agent-gateway/web/src/lib/gatewaySocket.ts index cad0bced..0186ddb5 100644 --- a/crates/agent-gateway/web/src/lib/gatewaySocket.ts +++ b/crates/agent-gateway/web/src/lib/gatewaySocket.ts @@ -78,6 +78,7 @@ type PendingRequest = { }; type ChatEventStreamOptions = { + runId?: string; afterSeq?: number; signal?: AbortSignal; }; @@ -1502,8 +1503,10 @@ export class GatewayWebSocketClient { if (signal?.aborted) { return; } + const normalizedRunId = options?.runId?.trim() ?? ""; for await (const event of streamGatewayChatEvents({ token: this.token, + runId: normalizedRunId || undefined, conversationId: normalizedConversationId, afterSeq: options?.afterSeq, signal, @@ -3444,8 +3447,10 @@ class SharedWorkerGatewayWebSocketClient implements GatewayWebSocketClientLike { if (signal?.aborted) { return; } + const normalizedRunId = options?.runId?.trim() ?? ""; for await (const event of streamGatewayChatEvents({ token: this.token, + runId: normalizedRunId || undefined, conversationId: normalizedConversationId, afterSeq: options?.afterSeq, signal, diff --git a/crates/agent-gateway/web/src/lib/gatewayTypes.ts b/crates/agent-gateway/web/src/lib/gatewayTypes.ts index 0ed1c8fe..cb583637 100644 --- a/crates/agent-gateway/web/src/lib/gatewayTypes.ts +++ b/crates/agent-gateway/web/src/lib/gatewayTypes.ts @@ -273,4 +273,9 @@ export type GatewayHistoryEvent = kind: "running" | "idle"; conversation_id: string; conversation?: ConversationSummary; + run_id?: string; + first_seq?: number; + latest_seq?: number; + run_epoch?: number; + updated_at?: number; }; From 2462dbe1450c3c61626f2da9230f94a42e2508af Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Tue, 23 Jun 2026 15:26:18 +0800 Subject: [PATCH 3/8] fix(chat): prevent edit-resend history drift --- .../internal/proto/v1/gateway.pb.go | 1025 ++++++++++------- .../internal/server/chat_commands.go | 22 +- .../internal/server/http_test.go | 12 +- .../server/websocket_history_handlers.go | 62 + .../internal/server/websocket_routes.go | 1 + .../internal/server/websocket_routes_test.go | 1 + crates/agent-gateway/proto/v1/gateway.proto | 21 + .../agent-gateway/web/src/app/GatewayApp.tsx | 141 +-- .../agent-gateway/web/src/app/historyUtils.ts | 70 +- .../web/src/components/GatewayTranscript.tsx | 59 +- .../agent-gateway/web/src/lib/agentTypes.ts | 1 + .../web/src/lib/chat/conversationState.ts | 157 ++- .../web/src/lib/chat/uploadedFiles.ts | 9 + crates/agent-gateway/web/src/lib/chatUi.ts | 18 +- .../web/src/lib/gatewaySocket.ts | 45 +- .../web/src/lib/gatewaySocket.worker.ts | 47 + .../src-tauri/src/services/gateway.rs | 84 +- .../src-tauri/src/services/gateway_bridge.rs | 396 ++++++- .../lib/chat/compaction/contextCompaction.ts | 9 +- .../chat/conversation/conversationState.ts | 157 ++- .../src/lib/chat/messages/uploadedFiles.ts | 10 + crates/agent-gui/src/pages/ChatPage.tsx | 4 - .../chat/gateway/useGatewayBridgeListeners.ts | 25 +- .../chat/transcript/TranscriptHistory.tsx | 21 +- 24 files changed, 1690 insertions(+), 707 deletions(-) diff --git a/crates/agent-gateway/internal/proto/v1/gateway.pb.go b/crates/agent-gateway/internal/proto/v1/gateway.pb.go index d22d54be..0a38a3c5 100644 --- a/crates/agent-gateway/internal/proto/v1/gateway.pb.go +++ b/crates/agent-gateway/internal/proto/v1/gateway.pb.go @@ -293,6 +293,7 @@ type GatewayEnvelope struct { // *GatewayEnvelope_HistoryGet // *GatewayEnvelope_HistoryRename // *GatewayEnvelope_HistoryDelete + // *GatewayEnvelope_HistoryPrefix // *GatewayEnvelope_HistoryPin // *GatewayEnvelope_HistoryShareGet // *GatewayEnvelope_HistoryShareSet @@ -437,6 +438,15 @@ func (x *GatewayEnvelope) GetHistoryDelete() *HistoryDeleteRequest { return nil } +func (x *GatewayEnvelope) GetHistoryPrefix() *HistoryPrefixRequest { + if x != nil { + if x, ok := x.Payload.(*GatewayEnvelope_HistoryPrefix); ok { + return x.HistoryPrefix + } + } + return nil +} + func (x *GatewayEnvelope) GetHistoryPin() *HistoryPinRequest { if x != nil { if x, ok := x.Payload.(*GatewayEnvelope_HistoryPin); ok { @@ -771,6 +781,10 @@ type GatewayEnvelope_HistoryDelete struct { HistoryDelete *HistoryDeleteRequest `protobuf:"bytes,33,opt,name=history_delete,json=historyDelete,proto3,oneof"` } +type GatewayEnvelope_HistoryPrefix struct { + HistoryPrefix *HistoryPrefixRequest `protobuf:"bytes,34,opt,name=history_prefix,json=historyPrefix,proto3,oneof"` +} + type GatewayEnvelope_HistoryPin struct { HistoryPin *HistoryPinRequest `protobuf:"bytes,35,opt,name=history_pin,json=historyPin,proto3,oneof"` } @@ -919,6 +933,8 @@ func (*GatewayEnvelope_HistoryRename) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_HistoryDelete) isGatewayEnvelope_Payload() {} +func (*GatewayEnvelope_HistoryPrefix) isGatewayEnvelope_Payload() {} + func (*GatewayEnvelope_HistoryPin) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_HistoryShareGet) isGatewayEnvelope_Payload() {} @@ -1000,6 +1016,7 @@ type AgentEnvelope struct { // *AgentEnvelope_HistoryRenameResp // *AgentEnvelope_HistoryDeleteResp // *AgentEnvelope_HistorySync + // *AgentEnvelope_HistoryPrefixResp // *AgentEnvelope_HistoryPinResp // *AgentEnvelope_HistoryShareGetResp // *AgentEnvelope_HistoryShareSetResp @@ -1159,6 +1176,15 @@ func (x *AgentEnvelope) GetHistorySync() *HistorySyncEvent { return nil } +func (x *AgentEnvelope) GetHistoryPrefixResp() *HistoryPrefixResponse { + if x != nil { + if x, ok := x.Payload.(*AgentEnvelope_HistoryPrefixResp); ok { + return x.HistoryPrefixResp + } + } + return nil +} + func (x *AgentEnvelope) GetHistoryPinResp() *HistoryPinResponse { if x != nil { if x, ok := x.Payload.(*AgentEnvelope_HistoryPinResp); ok { @@ -1551,6 +1577,10 @@ type AgentEnvelope_HistorySync struct { HistorySync *HistorySyncEvent `protobuf:"bytes,34,opt,name=history_sync,json=historySync,proto3,oneof"` } +type AgentEnvelope_HistoryPrefixResp struct { + HistoryPrefixResp *HistoryPrefixResponse `protobuf:"bytes,35,opt,name=history_prefix_resp,json=historyPrefixResp,proto3,oneof"` +} + type AgentEnvelope_HistoryPinResp struct { HistoryPinResp *HistoryPinResponse `protobuf:"bytes,36,opt,name=history_pin_resp,json=historyPinResp,proto3,oneof"` } @@ -1725,6 +1755,8 @@ func (*AgentEnvelope_HistoryDeleteResp) isAgentEnvelope_Payload() {} func (*AgentEnvelope_HistorySync) isAgentEnvelope_Payload() {} +func (*AgentEnvelope_HistoryPrefixResp) isAgentEnvelope_Payload() {} + func (*AgentEnvelope_HistoryPinResp) isAgentEnvelope_Payload() {} func (*AgentEnvelope_HistoryShareGetResp) isAgentEnvelope_Payload() {} @@ -4701,6 +4733,10 @@ type ChatMessageRef struct { state protoimpl.MessageState `protogen:"open.v1"` SegmentIndex int32 `protobuf:"varint,1,opt,name=segment_index,json=segmentIndex,proto3" json:"segment_index,omitempty"` MessageIndex int32 `protobuf:"varint,2,opt,name=message_index,json=messageIndex,proto3" json:"message_index,omitempty"` + SegmentId string `protobuf:"bytes,3,opt,name=segment_id,json=segmentId,proto3" json:"segment_id,omitempty"` + MessageId string `protobuf:"bytes,4,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` + Role string `protobuf:"bytes,5,opt,name=role,proto3" json:"role,omitempty"` + ContentHash string `protobuf:"bytes,6,opt,name=content_hash,json=contentHash,proto3" json:"content_hash,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4749,6 +4785,34 @@ func (x *ChatMessageRef) GetMessageIndex() int32 { return 0 } +func (x *ChatMessageRef) GetSegmentId() string { + if x != nil { + return x.SegmentId + } + return "" +} + +func (x *ChatMessageRef) GetMessageId() string { + if x != nil { + return x.MessageId + } + return "" +} + +func (x *ChatMessageRef) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *ChatMessageRef) GetContentHash() string { + if x != nil { + return x.ContentHash + } + return "" +} + type CancelChatRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` @@ -5605,6 +5669,150 @@ func (x *HistoryGetResponse) GetConversation() *ConversationSummary { return nil } +type HistoryPrefixRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` + MaxMessages int32 `protobuf:"varint,2,opt,name=max_messages,json=maxMessages,proto3" json:"max_messages,omitempty"` + BaseMessageRef *ChatMessageRef `protobuf:"bytes,3,opt,name=base_message_ref,json=baseMessageRef,proto3" json:"base_message_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HistoryPrefixRequest) Reset() { + *x = HistoryPrefixRequest{} + mi := &file_proto_v1_gateway_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HistoryPrefixRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HistoryPrefixRequest) ProtoMessage() {} + +func (x *HistoryPrefixRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HistoryPrefixRequest.ProtoReflect.Descriptor instead. +func (*HistoryPrefixRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{50} +} + +func (x *HistoryPrefixRequest) GetConversationId() string { + if x != nil { + return x.ConversationId + } + return "" +} + +func (x *HistoryPrefixRequest) GetMaxMessages() int32 { + if x != nil { + return x.MaxMessages + } + return 0 +} + +func (x *HistoryPrefixRequest) GetBaseMessageRef() *ChatMessageRef { + if x != nil { + return x.BaseMessageRef + } + return nil +} + +type HistoryPrefixResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` + MessagesJson string `protobuf:"bytes,2,opt,name=messages_json,json=messagesJson,proto3" json:"messages_json,omitempty"` + TotalMessageCount int32 `protobuf:"varint,3,opt,name=total_message_count,json=totalMessageCount,proto3" json:"total_message_count,omitempty"` + ReturnedMessageCount int32 `protobuf:"varint,4,opt,name=returned_message_count,json=returnedMessageCount,proto3" json:"returned_message_count,omitempty"` + HasMore bool `protobuf:"varint,5,opt,name=has_more,json=hasMore,proto3" json:"has_more,omitempty"` + Conversation *ConversationSummary `protobuf:"bytes,6,opt,name=conversation,proto3" json:"conversation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HistoryPrefixResponse) Reset() { + *x = HistoryPrefixResponse{} + mi := &file_proto_v1_gateway_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HistoryPrefixResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HistoryPrefixResponse) ProtoMessage() {} + +func (x *HistoryPrefixResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HistoryPrefixResponse.ProtoReflect.Descriptor instead. +func (*HistoryPrefixResponse) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{51} +} + +func (x *HistoryPrefixResponse) GetConversationId() string { + if x != nil { + return x.ConversationId + } + return "" +} + +func (x *HistoryPrefixResponse) GetMessagesJson() string { + if x != nil { + return x.MessagesJson + } + return "" +} + +func (x *HistoryPrefixResponse) GetTotalMessageCount() int32 { + if x != nil { + return x.TotalMessageCount + } + return 0 +} + +func (x *HistoryPrefixResponse) GetReturnedMessageCount() int32 { + if x != nil { + return x.ReturnedMessageCount + } + return 0 +} + +func (x *HistoryPrefixResponse) GetHasMore() bool { + if x != nil { + return x.HasMore + } + return false +} + +func (x *HistoryPrefixResponse) GetConversation() *ConversationSummary { + if x != nil { + return x.Conversation + } + return nil +} + type HistoryRenameRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` @@ -5615,7 +5823,7 @@ type HistoryRenameRequest struct { func (x *HistoryRenameRequest) Reset() { *x = HistoryRenameRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[50] + mi := &file_proto_v1_gateway_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5627,7 +5835,7 @@ func (x *HistoryRenameRequest) String() string { func (*HistoryRenameRequest) ProtoMessage() {} func (x *HistoryRenameRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[50] + mi := &file_proto_v1_gateway_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5640,7 +5848,7 @@ func (x *HistoryRenameRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryRenameRequest.ProtoReflect.Descriptor instead. func (*HistoryRenameRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{50} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{52} } func (x *HistoryRenameRequest) GetConversationId() string { @@ -5666,7 +5874,7 @@ type HistoryRenameResponse struct { func (x *HistoryRenameResponse) Reset() { *x = HistoryRenameResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[51] + mi := &file_proto_v1_gateway_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5678,7 +5886,7 @@ func (x *HistoryRenameResponse) String() string { func (*HistoryRenameResponse) ProtoMessage() {} func (x *HistoryRenameResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[51] + mi := &file_proto_v1_gateway_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5691,7 +5899,7 @@ func (x *HistoryRenameResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryRenameResponse.ProtoReflect.Descriptor instead. func (*HistoryRenameResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{51} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{53} } func (x *HistoryRenameResponse) GetConversation() *ConversationSummary { @@ -5711,7 +5919,7 @@ type HistoryPinRequest struct { func (x *HistoryPinRequest) Reset() { *x = HistoryPinRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[52] + mi := &file_proto_v1_gateway_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5723,7 +5931,7 @@ func (x *HistoryPinRequest) String() string { func (*HistoryPinRequest) ProtoMessage() {} func (x *HistoryPinRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[52] + mi := &file_proto_v1_gateway_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5736,7 +5944,7 @@ func (x *HistoryPinRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPinRequest.ProtoReflect.Descriptor instead. func (*HistoryPinRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{52} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{54} } func (x *HistoryPinRequest) GetConversationId() string { @@ -5762,7 +5970,7 @@ type HistoryPinResponse struct { func (x *HistoryPinResponse) Reset() { *x = HistoryPinResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[53] + mi := &file_proto_v1_gateway_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5774,7 +5982,7 @@ func (x *HistoryPinResponse) String() string { func (*HistoryPinResponse) ProtoMessage() {} func (x *HistoryPinResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[53] + mi := &file_proto_v1_gateway_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5787,7 +5995,7 @@ func (x *HistoryPinResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPinResponse.ProtoReflect.Descriptor instead. func (*HistoryPinResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{53} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{55} } func (x *HistoryPinResponse) GetConversation() *ConversationSummary { @@ -5811,7 +6019,7 @@ type HistoryShareStatus struct { func (x *HistoryShareStatus) Reset() { *x = HistoryShareStatus{} - mi := &file_proto_v1_gateway_proto_msgTypes[54] + mi := &file_proto_v1_gateway_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5823,7 +6031,7 @@ func (x *HistoryShareStatus) String() string { func (*HistoryShareStatus) ProtoMessage() {} func (x *HistoryShareStatus) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[54] + mi := &file_proto_v1_gateway_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5836,7 +6044,7 @@ func (x *HistoryShareStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareStatus.ProtoReflect.Descriptor instead. func (*HistoryShareStatus) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{54} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{56} } func (x *HistoryShareStatus) GetConversationId() string { @@ -5890,7 +6098,7 @@ type HistoryShareGetRequest struct { func (x *HistoryShareGetRequest) Reset() { *x = HistoryShareGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[55] + mi := &file_proto_v1_gateway_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5902,7 +6110,7 @@ func (x *HistoryShareGetRequest) String() string { func (*HistoryShareGetRequest) ProtoMessage() {} func (x *HistoryShareGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[55] + mi := &file_proto_v1_gateway_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5915,7 +6123,7 @@ func (x *HistoryShareGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareGetRequest.ProtoReflect.Descriptor instead. func (*HistoryShareGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{55} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{57} } func (x *HistoryShareGetRequest) GetConversationId() string { @@ -5934,7 +6142,7 @@ type HistoryShareGetResponse struct { func (x *HistoryShareGetResponse) Reset() { *x = HistoryShareGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[56] + mi := &file_proto_v1_gateway_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5946,7 +6154,7 @@ func (x *HistoryShareGetResponse) String() string { func (*HistoryShareGetResponse) ProtoMessage() {} func (x *HistoryShareGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[56] + mi := &file_proto_v1_gateway_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5959,7 +6167,7 @@ func (x *HistoryShareGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareGetResponse.ProtoReflect.Descriptor instead. func (*HistoryShareGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{56} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{58} } func (x *HistoryShareGetResponse) GetShare() *HistoryShareStatus { @@ -5980,7 +6188,7 @@ type HistoryShareSetRequest struct { func (x *HistoryShareSetRequest) Reset() { *x = HistoryShareSetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[57] + mi := &file_proto_v1_gateway_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5992,7 +6200,7 @@ func (x *HistoryShareSetRequest) String() string { func (*HistoryShareSetRequest) ProtoMessage() {} func (x *HistoryShareSetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[57] + mi := &file_proto_v1_gateway_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6005,7 +6213,7 @@ func (x *HistoryShareSetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareSetRequest.ProtoReflect.Descriptor instead. func (*HistoryShareSetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{57} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{59} } func (x *HistoryShareSetRequest) GetConversationId() string { @@ -6038,7 +6246,7 @@ type HistoryShareSetResponse struct { func (x *HistoryShareSetResponse) Reset() { *x = HistoryShareSetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[58] + mi := &file_proto_v1_gateway_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6050,7 +6258,7 @@ func (x *HistoryShareSetResponse) String() string { func (*HistoryShareSetResponse) ProtoMessage() {} func (x *HistoryShareSetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[58] + mi := &file_proto_v1_gateway_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6063,7 +6271,7 @@ func (x *HistoryShareSetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareSetResponse.ProtoReflect.Descriptor instead. func (*HistoryShareSetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{58} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{60} } func (x *HistoryShareSetResponse) GetShare() *HistoryShareStatus { @@ -6082,7 +6290,7 @@ type HistoryShareResolveRequest struct { func (x *HistoryShareResolveRequest) Reset() { *x = HistoryShareResolveRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[59] + mi := &file_proto_v1_gateway_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6094,7 +6302,7 @@ func (x *HistoryShareResolveRequest) String() string { func (*HistoryShareResolveRequest) ProtoMessage() {} func (x *HistoryShareResolveRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[59] + mi := &file_proto_v1_gateway_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6107,7 +6315,7 @@ func (x *HistoryShareResolveRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareResolveRequest.ProtoReflect.Descriptor instead. func (*HistoryShareResolveRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{59} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{61} } func (x *HistoryShareResolveRequest) GetToken() string { @@ -6130,7 +6338,7 @@ type HistoryShareResolveResponse struct { func (x *HistoryShareResolveResponse) Reset() { *x = HistoryShareResolveResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[60] + mi := &file_proto_v1_gateway_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6142,7 +6350,7 @@ func (x *HistoryShareResolveResponse) String() string { func (*HistoryShareResolveResponse) ProtoMessage() {} func (x *HistoryShareResolveResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[60] + mi := &file_proto_v1_gateway_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6155,7 +6363,7 @@ func (x *HistoryShareResolveResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareResolveResponse.ProtoReflect.Descriptor instead. func (*HistoryShareResolveResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{60} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{62} } func (x *HistoryShareResolveResponse) GetConversationId() string { @@ -6201,7 +6409,7 @@ type HistoryWorkdirsRequest struct { func (x *HistoryWorkdirsRequest) Reset() { *x = HistoryWorkdirsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[61] + mi := &file_proto_v1_gateway_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6213,7 +6421,7 @@ func (x *HistoryWorkdirsRequest) String() string { func (*HistoryWorkdirsRequest) ProtoMessage() {} func (x *HistoryWorkdirsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[61] + mi := &file_proto_v1_gateway_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6226,7 +6434,7 @@ func (x *HistoryWorkdirsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirsRequest.ProtoReflect.Descriptor instead. func (*HistoryWorkdirsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{61} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{63} } type HistoryWorkdirSummary struct { @@ -6240,7 +6448,7 @@ type HistoryWorkdirSummary struct { func (x *HistoryWorkdirSummary) Reset() { *x = HistoryWorkdirSummary{} - mi := &file_proto_v1_gateway_proto_msgTypes[62] + mi := &file_proto_v1_gateway_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6252,7 +6460,7 @@ func (x *HistoryWorkdirSummary) String() string { func (*HistoryWorkdirSummary) ProtoMessage() {} func (x *HistoryWorkdirSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[62] + mi := &file_proto_v1_gateway_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6265,7 +6473,7 @@ func (x *HistoryWorkdirSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirSummary.ProtoReflect.Descriptor instead. func (*HistoryWorkdirSummary) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{62} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{64} } func (x *HistoryWorkdirSummary) GetPath() string { @@ -6298,7 +6506,7 @@ type HistoryWorkdirsResponse struct { func (x *HistoryWorkdirsResponse) Reset() { *x = HistoryWorkdirsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[63] + mi := &file_proto_v1_gateway_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6310,7 +6518,7 @@ func (x *HistoryWorkdirsResponse) String() string { func (*HistoryWorkdirsResponse) ProtoMessage() {} func (x *HistoryWorkdirsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[63] + mi := &file_proto_v1_gateway_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6323,7 +6531,7 @@ func (x *HistoryWorkdirsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirsResponse.ProtoReflect.Descriptor instead. func (*HistoryWorkdirsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{63} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{65} } func (x *HistoryWorkdirsResponse) GetWorkdirs() []*HistoryWorkdirSummary { @@ -6342,7 +6550,7 @@ type HistoryDeleteRequest struct { func (x *HistoryDeleteRequest) Reset() { *x = HistoryDeleteRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[64] + mi := &file_proto_v1_gateway_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6354,7 +6562,7 @@ func (x *HistoryDeleteRequest) String() string { func (*HistoryDeleteRequest) ProtoMessage() {} func (x *HistoryDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[64] + mi := &file_proto_v1_gateway_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6367,7 +6575,7 @@ func (x *HistoryDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryDeleteRequest.ProtoReflect.Descriptor instead. func (*HistoryDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{64} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{66} } func (x *HistoryDeleteRequest) GetConversationId() string { @@ -6385,7 +6593,7 @@ type HistoryDeleteResponse struct { func (x *HistoryDeleteResponse) Reset() { *x = HistoryDeleteResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[65] + mi := &file_proto_v1_gateway_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6397,7 +6605,7 @@ func (x *HistoryDeleteResponse) String() string { func (*HistoryDeleteResponse) ProtoMessage() {} func (x *HistoryDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[65] + mi := &file_proto_v1_gateway_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6410,7 +6618,7 @@ func (x *HistoryDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryDeleteResponse.ProtoReflect.Descriptor instead. func (*HistoryDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{65} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{67} } type HistorySyncEvent struct { @@ -6424,7 +6632,7 @@ type HistorySyncEvent struct { func (x *HistorySyncEvent) Reset() { *x = HistorySyncEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[66] + mi := &file_proto_v1_gateway_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6436,7 +6644,7 @@ func (x *HistorySyncEvent) String() string { func (*HistorySyncEvent) ProtoMessage() {} func (x *HistorySyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[66] + mi := &file_proto_v1_gateway_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6449,7 +6657,7 @@ func (x *HistorySyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use HistorySyncEvent.ProtoReflect.Descriptor instead. func (*HistorySyncEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{66} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{68} } func (x *HistorySyncEvent) GetKind() string { @@ -6481,7 +6689,7 @@ type ProviderListRequest struct { func (x *ProviderListRequest) Reset() { *x = ProviderListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[67] + mi := &file_proto_v1_gateway_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6493,7 +6701,7 @@ func (x *ProviderListRequest) String() string { func (*ProviderListRequest) ProtoMessage() {} func (x *ProviderListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[67] + mi := &file_proto_v1_gateway_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6506,7 +6714,7 @@ func (x *ProviderListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderListRequest.ProtoReflect.Descriptor instead. func (*ProviderListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{67} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{69} } type ProviderListResponse struct { @@ -6518,7 +6726,7 @@ type ProviderListResponse struct { func (x *ProviderListResponse) Reset() { *x = ProviderListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[68] + mi := &file_proto_v1_gateway_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6530,7 +6738,7 @@ func (x *ProviderListResponse) String() string { func (*ProviderListResponse) ProtoMessage() {} func (x *ProviderListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[68] + mi := &file_proto_v1_gateway_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6543,7 +6751,7 @@ func (x *ProviderListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderListResponse.ProtoReflect.Descriptor instead. func (*ProviderListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{68} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{70} } func (x *ProviderListResponse) GetProvidersJson() string { @@ -6561,7 +6769,7 @@ type SettingsGetRequest struct { func (x *SettingsGetRequest) Reset() { *x = SettingsGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[69] + mi := &file_proto_v1_gateway_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6573,7 +6781,7 @@ func (x *SettingsGetRequest) String() string { func (*SettingsGetRequest) ProtoMessage() {} func (x *SettingsGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[69] + mi := &file_proto_v1_gateway_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6586,7 +6794,7 @@ func (x *SettingsGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsGetRequest.ProtoReflect.Descriptor instead. func (*SettingsGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{69} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{71} } type SettingsGetResponse struct { @@ -6598,7 +6806,7 @@ type SettingsGetResponse struct { func (x *SettingsGetResponse) Reset() { *x = SettingsGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[70] + mi := &file_proto_v1_gateway_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6610,7 +6818,7 @@ func (x *SettingsGetResponse) String() string { func (*SettingsGetResponse) ProtoMessage() {} func (x *SettingsGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[70] + mi := &file_proto_v1_gateway_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6623,7 +6831,7 @@ func (x *SettingsGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsGetResponse.ProtoReflect.Descriptor instead. func (*SettingsGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{70} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{72} } func (x *SettingsGetResponse) GetSettingsJson() string { @@ -6642,7 +6850,7 @@ type SettingsUpdateRequest struct { func (x *SettingsUpdateRequest) Reset() { *x = SettingsUpdateRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[71] + mi := &file_proto_v1_gateway_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6654,7 +6862,7 @@ func (x *SettingsUpdateRequest) String() string { func (*SettingsUpdateRequest) ProtoMessage() {} func (x *SettingsUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[71] + mi := &file_proto_v1_gateway_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6667,7 +6875,7 @@ func (x *SettingsUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsUpdateRequest.ProtoReflect.Descriptor instead. func (*SettingsUpdateRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{71} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{73} } func (x *SettingsUpdateRequest) GetSettingsJson() string { @@ -6687,7 +6895,7 @@ type SettingsUpdateResponse struct { func (x *SettingsUpdateResponse) Reset() { *x = SettingsUpdateResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[72] + mi := &file_proto_v1_gateway_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6699,7 +6907,7 @@ func (x *SettingsUpdateResponse) String() string { func (*SettingsUpdateResponse) ProtoMessage() {} func (x *SettingsUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[72] + mi := &file_proto_v1_gateway_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6712,7 +6920,7 @@ func (x *SettingsUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsUpdateResponse.ProtoReflect.Descriptor instead. func (*SettingsUpdateResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{72} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{74} } func (x *SettingsUpdateResponse) GetAccepted() bool { @@ -6739,7 +6947,7 @@ type SettingsResetSshKnownHostRequest struct { func (x *SettingsResetSshKnownHostRequest) Reset() { *x = SettingsResetSshKnownHostRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[73] + mi := &file_proto_v1_gateway_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6751,7 +6959,7 @@ func (x *SettingsResetSshKnownHostRequest) String() string { func (*SettingsResetSshKnownHostRequest) ProtoMessage() {} func (x *SettingsResetSshKnownHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[73] + mi := &file_proto_v1_gateway_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6764,7 +6972,7 @@ func (x *SettingsResetSshKnownHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsResetSshKnownHostRequest.ProtoReflect.Descriptor instead. func (*SettingsResetSshKnownHostRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{73} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{75} } func (x *SettingsResetSshKnownHostRequest) GetHost() string { @@ -6790,7 +6998,7 @@ type SettingsResetSshKnownHostResponse struct { func (x *SettingsResetSshKnownHostResponse) Reset() { *x = SettingsResetSshKnownHostResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[74] + mi := &file_proto_v1_gateway_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6802,7 +7010,7 @@ func (x *SettingsResetSshKnownHostResponse) String() string { func (*SettingsResetSshKnownHostResponse) ProtoMessage() {} func (x *SettingsResetSshKnownHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[74] + mi := &file_proto_v1_gateway_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6815,7 +7023,7 @@ func (x *SettingsResetSshKnownHostResponse) ProtoReflect() protoreflect.Message // Deprecated: Use SettingsResetSshKnownHostResponse.ProtoReflect.Descriptor instead. func (*SettingsResetSshKnownHostResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{74} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{76} } func (x *SettingsResetSshKnownHostResponse) GetDeleted() uint32 { @@ -6834,7 +7042,7 @@ type SettingsSyncEvent struct { func (x *SettingsSyncEvent) Reset() { *x = SettingsSyncEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[75] + mi := &file_proto_v1_gateway_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6846,7 +7054,7 @@ func (x *SettingsSyncEvent) String() string { func (*SettingsSyncEvent) ProtoMessage() {} func (x *SettingsSyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[75] + mi := &file_proto_v1_gateway_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6859,7 +7067,7 @@ func (x *SettingsSyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsSyncEvent.ProtoReflect.Descriptor instead. func (*SettingsSyncEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{75} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{77} } func (x *SettingsSyncEvent) GetSettingsJson() string { @@ -6877,7 +7085,7 @@ type SkillFilesListRequest struct { func (x *SkillFilesListRequest) Reset() { *x = SkillFilesListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[76] + mi := &file_proto_v1_gateway_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6889,7 +7097,7 @@ func (x *SkillFilesListRequest) String() string { func (*SkillFilesListRequest) ProtoMessage() {} func (x *SkillFilesListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[76] + mi := &file_proto_v1_gateway_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6902,7 +7110,7 @@ func (x *SkillFilesListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillFilesListRequest.ProtoReflect.Descriptor instead. func (*SkillFilesListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{76} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{78} } type SkillFilesListResponse struct { @@ -6916,7 +7124,7 @@ type SkillFilesListResponse struct { func (x *SkillFilesListResponse) Reset() { *x = SkillFilesListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[77] + mi := &file_proto_v1_gateway_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6928,7 +7136,7 @@ func (x *SkillFilesListResponse) String() string { func (*SkillFilesListResponse) ProtoMessage() {} func (x *SkillFilesListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[77] + mi := &file_proto_v1_gateway_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6941,7 +7149,7 @@ func (x *SkillFilesListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillFilesListResponse.ProtoReflect.Descriptor instead. func (*SkillFilesListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{77} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{79} } func (x *SkillFilesListResponse) GetRootDir() string { @@ -6974,7 +7182,7 @@ type SkillMetadataReadRequest struct { func (x *SkillMetadataReadRequest) Reset() { *x = SkillMetadataReadRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[78] + mi := &file_proto_v1_gateway_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6986,7 +7194,7 @@ func (x *SkillMetadataReadRequest) String() string { func (*SkillMetadataReadRequest) ProtoMessage() {} func (x *SkillMetadataReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[78] + mi := &file_proto_v1_gateway_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6999,7 +7207,7 @@ func (x *SkillMetadataReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillMetadataReadRequest.ProtoReflect.Descriptor instead. func (*SkillMetadataReadRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{78} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{80} } func (x *SkillMetadataReadRequest) GetPath() string { @@ -7019,7 +7227,7 @@ type SkillMetadataReadResponse struct { func (x *SkillMetadataReadResponse) Reset() { *x = SkillMetadataReadResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[79] + mi := &file_proto_v1_gateway_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7031,7 +7239,7 @@ func (x *SkillMetadataReadResponse) String() string { func (*SkillMetadataReadResponse) ProtoMessage() {} func (x *SkillMetadataReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[79] + mi := &file_proto_v1_gateway_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7044,7 +7252,7 @@ func (x *SkillMetadataReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillMetadataReadResponse.ProtoReflect.Descriptor instead. func (*SkillMetadataReadResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{79} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{81} } func (x *SkillMetadataReadResponse) GetName() string { @@ -7072,7 +7280,7 @@ type SkillTextReadRequest struct { func (x *SkillTextReadRequest) Reset() { *x = SkillTextReadRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[80] + mi := &file_proto_v1_gateway_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7084,7 +7292,7 @@ func (x *SkillTextReadRequest) String() string { func (*SkillTextReadRequest) ProtoMessage() {} func (x *SkillTextReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[80] + mi := &file_proto_v1_gateway_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7097,7 +7305,7 @@ func (x *SkillTextReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillTextReadRequest.ProtoReflect.Descriptor instead. func (*SkillTextReadRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{80} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{82} } func (x *SkillTextReadRequest) GetPath() string { @@ -7131,7 +7339,7 @@ type SkillTextReadResponse struct { func (x *SkillTextReadResponse) Reset() { *x = SkillTextReadResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[81] + mi := &file_proto_v1_gateway_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7143,7 +7351,7 @@ func (x *SkillTextReadResponse) String() string { func (*SkillTextReadResponse) ProtoMessage() {} func (x *SkillTextReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[81] + mi := &file_proto_v1_gateway_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7156,7 +7364,7 @@ func (x *SkillTextReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillTextReadResponse.ProtoReflect.Descriptor instead. func (*SkillTextReadResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{81} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{83} } func (x *SkillTextReadResponse) GetContent() string { @@ -7182,7 +7390,7 @@ type SkillManageRequest struct { func (x *SkillManageRequest) Reset() { *x = SkillManageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[82] + mi := &file_proto_v1_gateway_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7194,7 +7402,7 @@ func (x *SkillManageRequest) String() string { func (*SkillManageRequest) ProtoMessage() {} func (x *SkillManageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[82] + mi := &file_proto_v1_gateway_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7207,7 +7415,7 @@ func (x *SkillManageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillManageRequest.ProtoReflect.Descriptor instead. func (*SkillManageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{82} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{84} } func (x *SkillManageRequest) GetPayloadJson() string { @@ -7226,7 +7434,7 @@ type SkillManageResponse struct { func (x *SkillManageResponse) Reset() { *x = SkillManageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[83] + mi := &file_proto_v1_gateway_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7238,7 +7446,7 @@ func (x *SkillManageResponse) String() string { func (*SkillManageResponse) ProtoMessage() {} func (x *SkillManageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[83] + mi := &file_proto_v1_gateway_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7251,7 +7459,7 @@ func (x *SkillManageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillManageResponse.ProtoReflect.Descriptor instead. func (*SkillManageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{83} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{85} } func (x *SkillManageResponse) GetResultJson() string { @@ -7272,7 +7480,7 @@ type FileMentionListRequest struct { func (x *FileMentionListRequest) Reset() { *x = FileMentionListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[84] + mi := &file_proto_v1_gateway_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7284,7 +7492,7 @@ func (x *FileMentionListRequest) String() string { func (*FileMentionListRequest) ProtoMessage() {} func (x *FileMentionListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[84] + mi := &file_proto_v1_gateway_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7297,7 +7505,7 @@ func (x *FileMentionListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionListRequest.ProtoReflect.Descriptor instead. func (*FileMentionListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{84} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{86} } func (x *FileMentionListRequest) GetWorkdir() string { @@ -7331,7 +7539,7 @@ type FileMentionEntry struct { func (x *FileMentionEntry) Reset() { *x = FileMentionEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[85] + mi := &file_proto_v1_gateway_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7343,7 +7551,7 @@ func (x *FileMentionEntry) String() string { func (*FileMentionEntry) ProtoMessage() {} func (x *FileMentionEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[85] + mi := &file_proto_v1_gateway_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7356,7 +7564,7 @@ func (x *FileMentionEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionEntry.ProtoReflect.Descriptor instead. func (*FileMentionEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{85} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{87} } func (x *FileMentionEntry) GetPath() string { @@ -7383,7 +7591,7 @@ type FileMentionListResponse struct { func (x *FileMentionListResponse) Reset() { *x = FileMentionListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[86] + mi := &file_proto_v1_gateway_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7395,7 +7603,7 @@ func (x *FileMentionListResponse) String() string { func (*FileMentionListResponse) ProtoMessage() {} func (x *FileMentionListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[86] + mi := &file_proto_v1_gateway_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7408,7 +7616,7 @@ func (x *FileMentionListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionListResponse.ProtoReflect.Descriptor instead. func (*FileMentionListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{86} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{88} } func (x *FileMentionListResponse) GetEntries() []*FileMentionEntry { @@ -7437,7 +7645,7 @@ type FsRoot struct { func (x *FsRoot) Reset() { *x = FsRoot{} - mi := &file_proto_v1_gateway_proto_msgTypes[87] + mi := &file_proto_v1_gateway_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7449,7 +7657,7 @@ func (x *FsRoot) String() string { func (*FsRoot) ProtoMessage() {} func (x *FsRoot) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[87] + mi := &file_proto_v1_gateway_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7462,7 +7670,7 @@ func (x *FsRoot) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRoot.ProtoReflect.Descriptor instead. func (*FsRoot) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{87} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{89} } func (x *FsRoot) GetId() string { @@ -7501,7 +7709,7 @@ type FsRootsRequest struct { func (x *FsRootsRequest) Reset() { *x = FsRootsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[88] + mi := &file_proto_v1_gateway_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7513,7 +7721,7 @@ func (x *FsRootsRequest) String() string { func (*FsRootsRequest) ProtoMessage() {} func (x *FsRootsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[88] + mi := &file_proto_v1_gateway_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7526,7 +7734,7 @@ func (x *FsRootsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRootsRequest.ProtoReflect.Descriptor instead. func (*FsRootsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{88} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{90} } type FsRootsResponse struct { @@ -7538,7 +7746,7 @@ type FsRootsResponse struct { func (x *FsRootsResponse) Reset() { *x = FsRootsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[89] + mi := &file_proto_v1_gateway_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7550,7 +7758,7 @@ func (x *FsRootsResponse) String() string { func (*FsRootsResponse) ProtoMessage() {} func (x *FsRootsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[89] + mi := &file_proto_v1_gateway_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7563,7 +7771,7 @@ func (x *FsRootsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRootsResponse.ProtoReflect.Descriptor instead. func (*FsRootsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{89} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{91} } func (x *FsRootsResponse) GetRoots() []*FsRoot { @@ -7583,7 +7791,7 @@ type FsListDirsRequest struct { func (x *FsListDirsRequest) Reset() { *x = FsListDirsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[90] + mi := &file_proto_v1_gateway_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7595,7 +7803,7 @@ func (x *FsListDirsRequest) String() string { func (*FsListDirsRequest) ProtoMessage() {} func (x *FsListDirsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[90] + mi := &file_proto_v1_gateway_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7608,7 +7816,7 @@ func (x *FsListDirsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListDirsRequest.ProtoReflect.Descriptor instead. func (*FsListDirsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{90} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{92} } func (x *FsListDirsRequest) GetPath() string { @@ -7635,7 +7843,7 @@ type FsDirEntry struct { func (x *FsDirEntry) Reset() { *x = FsDirEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[91] + mi := &file_proto_v1_gateway_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7647,7 +7855,7 @@ func (x *FsDirEntry) String() string { func (*FsDirEntry) ProtoMessage() {} func (x *FsDirEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[91] + mi := &file_proto_v1_gateway_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7660,7 +7868,7 @@ func (x *FsDirEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDirEntry.ProtoReflect.Descriptor instead. func (*FsDirEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{91} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{93} } func (x *FsDirEntry) GetPath() string { @@ -7688,7 +7896,7 @@ type FsListDirsResponse struct { func (x *FsListDirsResponse) Reset() { *x = FsListDirsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[92] + mi := &file_proto_v1_gateway_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7700,7 +7908,7 @@ func (x *FsListDirsResponse) String() string { func (*FsListDirsResponse) ProtoMessage() {} func (x *FsListDirsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[92] + mi := &file_proto_v1_gateway_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7713,7 +7921,7 @@ func (x *FsListDirsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListDirsResponse.ProtoReflect.Descriptor instead. func (*FsListDirsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{92} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{94} } func (x *FsListDirsResponse) GetPath() string { @@ -7747,7 +7955,7 @@ type FsCreateProjectFolderRequest struct { func (x *FsCreateProjectFolderRequest) Reset() { *x = FsCreateProjectFolderRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[93] + mi := &file_proto_v1_gateway_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7759,7 +7967,7 @@ func (x *FsCreateProjectFolderRequest) String() string { func (*FsCreateProjectFolderRequest) ProtoMessage() {} func (x *FsCreateProjectFolderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[93] + mi := &file_proto_v1_gateway_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7772,7 +7980,7 @@ func (x *FsCreateProjectFolderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateProjectFolderRequest.ProtoReflect.Descriptor instead. func (*FsCreateProjectFolderRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{93} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{95} } func (x *FsCreateProjectFolderRequest) GetParent() string { @@ -7798,7 +8006,7 @@ type FsCreateProjectFolderResponse struct { func (x *FsCreateProjectFolderResponse) Reset() { *x = FsCreateProjectFolderResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[94] + mi := &file_proto_v1_gateway_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7810,7 +8018,7 @@ func (x *FsCreateProjectFolderResponse) String() string { func (*FsCreateProjectFolderResponse) ProtoMessage() {} func (x *FsCreateProjectFolderResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[94] + mi := &file_proto_v1_gateway_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7823,7 +8031,7 @@ func (x *FsCreateProjectFolderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateProjectFolderResponse.ProtoReflect.Descriptor instead. func (*FsCreateProjectFolderResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{94} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{96} } func (x *FsCreateProjectFolderResponse) GetPath() string { @@ -7846,7 +8054,7 @@ type FsListRequest struct { func (x *FsListRequest) Reset() { *x = FsListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[95] + mi := &file_proto_v1_gateway_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7858,7 +8066,7 @@ func (x *FsListRequest) String() string { func (*FsListRequest) ProtoMessage() {} func (x *FsListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[95] + mi := &file_proto_v1_gateway_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7871,7 +8079,7 @@ func (x *FsListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListRequest.ProtoReflect.Descriptor instead. func (*FsListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{95} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{97} } func (x *FsListRequest) GetWorkdir() string { @@ -7919,7 +8127,7 @@ type FsListEntry struct { func (x *FsListEntry) Reset() { *x = FsListEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[96] + mi := &file_proto_v1_gateway_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7931,7 +8139,7 @@ func (x *FsListEntry) String() string { func (*FsListEntry) ProtoMessage() {} func (x *FsListEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[96] + mi := &file_proto_v1_gateway_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7944,7 +8152,7 @@ func (x *FsListEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListEntry.ProtoReflect.Descriptor instead. func (*FsListEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{96} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{98} } func (x *FsListEntry) GetPath() string { @@ -7977,7 +8185,7 @@ type FsListResponse struct { func (x *FsListResponse) Reset() { *x = FsListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[97] + mi := &file_proto_v1_gateway_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7989,7 +8197,7 @@ func (x *FsListResponse) String() string { func (*FsListResponse) ProtoMessage() {} func (x *FsListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[97] + mi := &file_proto_v1_gateway_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8002,7 +8210,7 @@ func (x *FsListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListResponse.ProtoReflect.Descriptor instead. func (*FsListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{97} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{99} } func (x *FsListResponse) GetPath() string { @@ -8071,7 +8279,7 @@ type FsReadEditableTextRequest struct { func (x *FsReadEditableTextRequest) Reset() { *x = FsReadEditableTextRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[98] + mi := &file_proto_v1_gateway_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8083,7 +8291,7 @@ func (x *FsReadEditableTextRequest) String() string { func (*FsReadEditableTextRequest) ProtoMessage() {} func (x *FsReadEditableTextRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[98] + mi := &file_proto_v1_gateway_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8096,7 +8304,7 @@ func (x *FsReadEditableTextRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadEditableTextRequest.ProtoReflect.Descriptor instead. func (*FsReadEditableTextRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{98} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{100} } func (x *FsReadEditableTextRequest) GetWorkdir() string { @@ -8127,7 +8335,7 @@ type FsReadEditableTextResponse struct { func (x *FsReadEditableTextResponse) Reset() { *x = FsReadEditableTextResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[99] + mi := &file_proto_v1_gateway_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8139,7 +8347,7 @@ func (x *FsReadEditableTextResponse) String() string { func (*FsReadEditableTextResponse) ProtoMessage() {} func (x *FsReadEditableTextResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[99] + mi := &file_proto_v1_gateway_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8152,7 +8360,7 @@ func (x *FsReadEditableTextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadEditableTextResponse.ProtoReflect.Descriptor instead. func (*FsReadEditableTextResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{99} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{101} } func (x *FsReadEditableTextResponse) GetPath() string { @@ -8207,7 +8415,7 @@ type FsReadWorkspaceImageRequest struct { func (x *FsReadWorkspaceImageRequest) Reset() { *x = FsReadWorkspaceImageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[100] + mi := &file_proto_v1_gateway_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8219,7 +8427,7 @@ func (x *FsReadWorkspaceImageRequest) String() string { func (*FsReadWorkspaceImageRequest) ProtoMessage() {} func (x *FsReadWorkspaceImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[100] + mi := &file_proto_v1_gateway_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8232,7 +8440,7 @@ func (x *FsReadWorkspaceImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadWorkspaceImageRequest.ProtoReflect.Descriptor instead. func (*FsReadWorkspaceImageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{100} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{102} } func (x *FsReadWorkspaceImageRequest) GetWorkdir() string { @@ -8263,7 +8471,7 @@ type FsReadWorkspaceImageResponse struct { func (x *FsReadWorkspaceImageResponse) Reset() { *x = FsReadWorkspaceImageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[101] + mi := &file_proto_v1_gateway_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8275,7 +8483,7 @@ func (x *FsReadWorkspaceImageResponse) String() string { func (*FsReadWorkspaceImageResponse) ProtoMessage() {} func (x *FsReadWorkspaceImageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[101] + mi := &file_proto_v1_gateway_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8288,7 +8496,7 @@ func (x *FsReadWorkspaceImageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadWorkspaceImageResponse.ProtoReflect.Descriptor instead. func (*FsReadWorkspaceImageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{101} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{103} } func (x *FsReadWorkspaceImageResponse) GetPath() string { @@ -8349,7 +8557,7 @@ type FsWriteTextRequest struct { func (x *FsWriteTextRequest) Reset() { *x = FsWriteTextRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[102] + mi := &file_proto_v1_gateway_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8361,7 +8569,7 @@ func (x *FsWriteTextRequest) String() string { func (*FsWriteTextRequest) ProtoMessage() {} func (x *FsWriteTextRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[102] + mi := &file_proto_v1_gateway_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8374,7 +8582,7 @@ func (x *FsWriteTextRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsWriteTextRequest.ProtoReflect.Descriptor instead. func (*FsWriteTextRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{102} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{104} } func (x *FsWriteTextRequest) GetWorkdir() string { @@ -8448,7 +8656,7 @@ type FsWriteTextResponse struct { func (x *FsWriteTextResponse) Reset() { *x = FsWriteTextResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[103] + mi := &file_proto_v1_gateway_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8460,7 +8668,7 @@ func (x *FsWriteTextResponse) String() string { func (*FsWriteTextResponse) ProtoMessage() {} func (x *FsWriteTextResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[103] + mi := &file_proto_v1_gateway_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8473,7 +8681,7 @@ func (x *FsWriteTextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsWriteTextResponse.ProtoReflect.Descriptor instead. func (*FsWriteTextResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{103} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{105} } func (x *FsWriteTextResponse) GetPath() string { @@ -8535,7 +8743,7 @@ type FsCreateDirRequest struct { func (x *FsCreateDirRequest) Reset() { *x = FsCreateDirRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[104] + mi := &file_proto_v1_gateway_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8547,7 +8755,7 @@ func (x *FsCreateDirRequest) String() string { func (*FsCreateDirRequest) ProtoMessage() {} func (x *FsCreateDirRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[104] + mi := &file_proto_v1_gateway_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8560,7 +8768,7 @@ func (x *FsCreateDirRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateDirRequest.ProtoReflect.Descriptor instead. func (*FsCreateDirRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{104} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{106} } func (x *FsCreateDirRequest) GetWorkdir() string { @@ -8587,7 +8795,7 @@ type FsCreateDirResponse struct { func (x *FsCreateDirResponse) Reset() { *x = FsCreateDirResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[105] + mi := &file_proto_v1_gateway_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8599,7 +8807,7 @@ func (x *FsCreateDirResponse) String() string { func (*FsCreateDirResponse) ProtoMessage() {} func (x *FsCreateDirResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[105] + mi := &file_proto_v1_gateway_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8612,7 +8820,7 @@ func (x *FsCreateDirResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateDirResponse.ProtoReflect.Descriptor instead. func (*FsCreateDirResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{105} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{107} } func (x *FsCreateDirResponse) GetPath() string { @@ -8640,7 +8848,7 @@ type FsRenameRequest struct { func (x *FsRenameRequest) Reset() { *x = FsRenameRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[106] + mi := &file_proto_v1_gateway_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8652,7 +8860,7 @@ func (x *FsRenameRequest) String() string { func (*FsRenameRequest) ProtoMessage() {} func (x *FsRenameRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[106] + mi := &file_proto_v1_gateway_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8665,7 +8873,7 @@ func (x *FsRenameRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRenameRequest.ProtoReflect.Descriptor instead. func (*FsRenameRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{106} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{108} } func (x *FsRenameRequest) GetWorkdir() string { @@ -8700,7 +8908,7 @@ type FsRenameResponse struct { func (x *FsRenameResponse) Reset() { *x = FsRenameResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[107] + mi := &file_proto_v1_gateway_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8712,7 +8920,7 @@ func (x *FsRenameResponse) String() string { func (*FsRenameResponse) ProtoMessage() {} func (x *FsRenameResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[107] + mi := &file_proto_v1_gateway_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8725,7 +8933,7 @@ func (x *FsRenameResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRenameResponse.ProtoReflect.Descriptor instead. func (*FsRenameResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{107} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{109} } func (x *FsRenameResponse) GetFromPath() string { @@ -8759,7 +8967,7 @@ type FsDeleteRequest struct { func (x *FsDeleteRequest) Reset() { *x = FsDeleteRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[108] + mi := &file_proto_v1_gateway_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8771,7 +8979,7 @@ func (x *FsDeleteRequest) String() string { func (*FsDeleteRequest) ProtoMessage() {} func (x *FsDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[108] + mi := &file_proto_v1_gateway_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8784,7 +8992,7 @@ func (x *FsDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDeleteRequest.ProtoReflect.Descriptor instead. func (*FsDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{108} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{110} } func (x *FsDeleteRequest) GetWorkdir() string { @@ -8811,7 +9019,7 @@ type FsDeleteResponse struct { func (x *FsDeleteResponse) Reset() { *x = FsDeleteResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[109] + mi := &file_proto_v1_gateway_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8823,7 +9031,7 @@ func (x *FsDeleteResponse) String() string { func (*FsDeleteResponse) ProtoMessage() {} func (x *FsDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[109] + mi := &file_proto_v1_gateway_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8836,7 +9044,7 @@ func (x *FsDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDeleteResponse.ProtoReflect.Descriptor instead. func (*FsDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{109} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{111} } func (x *FsDeleteResponse) GetPath() string { @@ -8862,7 +9070,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[110] + mi := &file_proto_v1_gateway_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8874,7 +9082,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[110] + mi := &file_proto_v1_gateway_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8887,7 +9095,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{110} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{112} } func (x *PingRequest) GetTimestamp() int64 { @@ -8906,7 +9114,7 @@ type PongResponse struct { func (x *PongResponse) Reset() { *x = PongResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[111] + mi := &file_proto_v1_gateway_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8918,7 +9126,7 @@ func (x *PongResponse) String() string { func (*PongResponse) ProtoMessage() {} func (x *PongResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[111] + mi := &file_proto_v1_gateway_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8931,7 +9139,7 @@ func (x *PongResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PongResponse.ProtoReflect.Descriptor instead. func (*PongResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{111} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{113} } func (x *PongResponse) GetTimestamp() int64 { @@ -8951,7 +9159,7 @@ type ErrorResponse struct { func (x *ErrorResponse) Reset() { *x = ErrorResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[112] + mi := &file_proto_v1_gateway_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8963,7 +9171,7 @@ func (x *ErrorResponse) String() string { func (*ErrorResponse) ProtoMessage() {} func (x *ErrorResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[112] + mi := &file_proto_v1_gateway_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8976,7 +9184,7 @@ func (x *ErrorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ErrorResponse.ProtoReflect.Descriptor instead. func (*ErrorResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{112} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{114} } func (x *ErrorResponse) GetCode() int32 { @@ -9006,7 +9214,7 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12\x1d\n" + "\n" + - "session_id\x18\x03 \x01(\tR\tsessionId\"\xa9\x1b\n" + + "session_id\x18\x03 \x01(\tR\tsessionId\"\xfe\x1b\n" + "\x0fGatewayEnvelope\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1c\n" + @@ -9019,7 +9227,8 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\vhistory_get\x18\x1f \x01(\v2'.liveagent.gateway.v1.HistoryGetRequestH\x00R\n" + "historyGet\x12S\n" + "\x0ehistory_rename\x18 \x01(\v2*.liveagent.gateway.v1.HistoryRenameRequestH\x00R\rhistoryRename\x12S\n" + - "\x0ehistory_delete\x18! \x01(\v2*.liveagent.gateway.v1.HistoryDeleteRequestH\x00R\rhistoryDelete\x12J\n" + + "\x0ehistory_delete\x18! \x01(\v2*.liveagent.gateway.v1.HistoryDeleteRequestH\x00R\rhistoryDelete\x12S\n" + + "\x0ehistory_prefix\x18\" \x01(\v2*.liveagent.gateway.v1.HistoryPrefixRequestH\x00R\rhistoryPrefix\x12J\n" + "\vhistory_pin\x18# \x01(\v2'.liveagent.gateway.v1.HistoryPinRequestH\x00R\n" + "historyPin\x12Z\n" + "\x11history_share_get\x18$ \x01(\v2,.liveagent.gateway.v1.HistoryShareGetRequestH\x00R\x0fhistoryShareGet\x12Z\n" + @@ -9057,7 +9266,7 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x13tunnel_control_resp\x18D \x01(\v2+.liveagent.gateway.v1.TunnelControlResponseH\x00R\x11tunnelControlResp\x12F\n" + "\ftunnel_frame\x18E \x01(\v2!.liveagent.gateway.v1.TunnelFrameH\x00R\vtunnelFrame\x12z\n" + "\x1dsettings_reset_ssh_known_host\x18H \x01(\v26.liveagent.gateway.v1.SettingsResetSshKnownHostRequestH\x00R\x19settingsResetSshKnownHostB\t\n" + - "\apayload\"\xef!\n" + + "\apayload\"\xce\"\n" + "\rAgentEnvelope\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1c\n" + @@ -9070,7 +9279,8 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x10history_get_resp\x18\x1f \x01(\v2(.liveagent.gateway.v1.HistoryGetResponseH\x00R\x0ehistoryGetResp\x12]\n" + "\x13history_rename_resp\x18 \x01(\v2+.liveagent.gateway.v1.HistoryRenameResponseH\x00R\x11historyRenameResp\x12]\n" + "\x13history_delete_resp\x18! \x01(\v2+.liveagent.gateway.v1.HistoryDeleteResponseH\x00R\x11historyDeleteResp\x12K\n" + - "\fhistory_sync\x18\" \x01(\v2&.liveagent.gateway.v1.HistorySyncEventH\x00R\vhistorySync\x12T\n" + + "\fhistory_sync\x18\" \x01(\v2&.liveagent.gateway.v1.HistorySyncEventH\x00R\vhistorySync\x12]\n" + + "\x13history_prefix_resp\x18# \x01(\v2+.liveagent.gateway.v1.HistoryPrefixResponseH\x00R\x11historyPrefixResp\x12T\n" + "\x10history_pin_resp\x18$ \x01(\v2(.liveagent.gateway.v1.HistoryPinResponseH\x00R\x0ehistoryPinResp\x12d\n" + "\x16history_share_get_resp\x18% \x01(\v2-.liveagent.gateway.v1.HistoryShareGetResponseH\x00R\x13historyShareGetResp\x12d\n" + "\x16history_share_set_resp\x18& \x01(\v2-.liveagent.gateway.v1.HistoryShareSetResponseH\x00R\x13historyShareSetResp\x12p\n" + @@ -9410,10 +9620,16 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x15selected_system_tools\x18\x06 \x03(\tR\x13selectedSystemTools\x12M\n" + "\x0euploaded_files\x18\a \x03(\v2&.liveagent.gateway.v1.ChatUploadedFileR\ruploadedFiles\x12*\n" + "\x11client_request_id\x18\b \x01(\tR\x0fclientRequestId\x12T\n" + - "\x10runtime_controls\x18\t \x01(\v2).liveagent.gateway.v1.ChatRuntimeControlsR\x0fruntimeControls\"Z\n" + + "\x10runtime_controls\x18\t \x01(\v2).liveagent.gateway.v1.ChatRuntimeControlsR\x0fruntimeControls\"\xcf\x01\n" + "\x0eChatMessageRef\x12#\n" + "\rsegment_index\x18\x01 \x01(\x05R\fsegmentIndex\x12#\n" + - "\rmessage_index\x18\x02 \x01(\x05R\fmessageIndex\"<\n" + + "\rmessage_index\x18\x02 \x01(\x05R\fmessageIndex\x12\x1d\n" + + "\n" + + "segment_id\x18\x03 \x01(\tR\tsegmentId\x12\x1d\n" + + "\n" + + "message_id\x18\x04 \x01(\tR\tmessageId\x12\x12\n" + + "\x04role\x18\x05 \x01(\tR\x04role\x12!\n" + + "\fcontent_hash\x18\x06 \x01(\tR\vcontentHash\"<\n" + "\x11CancelChatRequest\x12'\n" + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\"\xf6\x01\n" + "\x12ChatCommandRequest\x12\x12\n" + @@ -9496,6 +9712,17 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x13total_message_count\x18\x03 \x01(\x05R\x11totalMessageCount\x124\n" + "\x16returned_message_count\x18\x04 \x01(\x05R\x14returnedMessageCount\x12\x19\n" + "\bhas_more\x18\x05 \x01(\bR\ahasMore\x12M\n" + + "\fconversation\x18\x06 \x01(\v2).liveagent.gateway.v1.ConversationSummaryR\fconversation\"\xb2\x01\n" + + "\x14HistoryPrefixRequest\x12'\n" + + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12!\n" + + "\fmax_messages\x18\x02 \x01(\x05R\vmaxMessages\x12N\n" + + "\x10base_message_ref\x18\x03 \x01(\v2$.liveagent.gateway.v1.ChatMessageRefR\x0ebaseMessageRef\"\xb5\x02\n" + + "\x15HistoryPrefixResponse\x12'\n" + + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12#\n" + + "\rmessages_json\x18\x02 \x01(\tR\fmessagesJson\x12.\n" + + "\x13total_message_count\x18\x03 \x01(\x05R\x11totalMessageCount\x124\n" + + "\x16returned_message_count\x18\x04 \x01(\x05R\x14returnedMessageCount\x12\x19\n" + + "\bhas_more\x18\x05 \x01(\bR\ahasMore\x12M\n" + "\fconversation\x18\x06 \x01(\v2).liveagent.gateway.v1.ConversationSummaryR\fconversation\"U\n" + "\x14HistoryRenameRequest\x12'\n" + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12\x14\n" + @@ -9746,7 +9973,7 @@ func file_proto_v1_gateway_proto_rawDescGZIP() []byte { } var file_proto_v1_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_proto_v1_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 113) +var file_proto_v1_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 115) var file_proto_v1_gateway_proto_goTypes = []any{ (TunnelFrameKind)(0), // 0: liveagent.gateway.v1.TunnelFrameKind (ChatEvent_ChatEventType)(0), // 1: liveagent.gateway.v1.ChatEvent.ChatEventType @@ -9800,209 +10027,215 @@ var file_proto_v1_gateway_proto_goTypes = []any{ (*ConversationSummary)(nil), // 49: liveagent.gateway.v1.ConversationSummary (*HistoryGetRequest)(nil), // 50: liveagent.gateway.v1.HistoryGetRequest (*HistoryGetResponse)(nil), // 51: liveagent.gateway.v1.HistoryGetResponse - (*HistoryRenameRequest)(nil), // 52: liveagent.gateway.v1.HistoryRenameRequest - (*HistoryRenameResponse)(nil), // 53: liveagent.gateway.v1.HistoryRenameResponse - (*HistoryPinRequest)(nil), // 54: liveagent.gateway.v1.HistoryPinRequest - (*HistoryPinResponse)(nil), // 55: liveagent.gateway.v1.HistoryPinResponse - (*HistoryShareStatus)(nil), // 56: liveagent.gateway.v1.HistoryShareStatus - (*HistoryShareGetRequest)(nil), // 57: liveagent.gateway.v1.HistoryShareGetRequest - (*HistoryShareGetResponse)(nil), // 58: liveagent.gateway.v1.HistoryShareGetResponse - (*HistoryShareSetRequest)(nil), // 59: liveagent.gateway.v1.HistoryShareSetRequest - (*HistoryShareSetResponse)(nil), // 60: liveagent.gateway.v1.HistoryShareSetResponse - (*HistoryShareResolveRequest)(nil), // 61: liveagent.gateway.v1.HistoryShareResolveRequest - (*HistoryShareResolveResponse)(nil), // 62: liveagent.gateway.v1.HistoryShareResolveResponse - (*HistoryWorkdirsRequest)(nil), // 63: liveagent.gateway.v1.HistoryWorkdirsRequest - (*HistoryWorkdirSummary)(nil), // 64: liveagent.gateway.v1.HistoryWorkdirSummary - (*HistoryWorkdirsResponse)(nil), // 65: liveagent.gateway.v1.HistoryWorkdirsResponse - (*HistoryDeleteRequest)(nil), // 66: liveagent.gateway.v1.HistoryDeleteRequest - (*HistoryDeleteResponse)(nil), // 67: liveagent.gateway.v1.HistoryDeleteResponse - (*HistorySyncEvent)(nil), // 68: liveagent.gateway.v1.HistorySyncEvent - (*ProviderListRequest)(nil), // 69: liveagent.gateway.v1.ProviderListRequest - (*ProviderListResponse)(nil), // 70: liveagent.gateway.v1.ProviderListResponse - (*SettingsGetRequest)(nil), // 71: liveagent.gateway.v1.SettingsGetRequest - (*SettingsGetResponse)(nil), // 72: liveagent.gateway.v1.SettingsGetResponse - (*SettingsUpdateRequest)(nil), // 73: liveagent.gateway.v1.SettingsUpdateRequest - (*SettingsUpdateResponse)(nil), // 74: liveagent.gateway.v1.SettingsUpdateResponse - (*SettingsResetSshKnownHostRequest)(nil), // 75: liveagent.gateway.v1.SettingsResetSshKnownHostRequest - (*SettingsResetSshKnownHostResponse)(nil), // 76: liveagent.gateway.v1.SettingsResetSshKnownHostResponse - (*SettingsSyncEvent)(nil), // 77: liveagent.gateway.v1.SettingsSyncEvent - (*SkillFilesListRequest)(nil), // 78: liveagent.gateway.v1.SkillFilesListRequest - (*SkillFilesListResponse)(nil), // 79: liveagent.gateway.v1.SkillFilesListResponse - (*SkillMetadataReadRequest)(nil), // 80: liveagent.gateway.v1.SkillMetadataReadRequest - (*SkillMetadataReadResponse)(nil), // 81: liveagent.gateway.v1.SkillMetadataReadResponse - (*SkillTextReadRequest)(nil), // 82: liveagent.gateway.v1.SkillTextReadRequest - (*SkillTextReadResponse)(nil), // 83: liveagent.gateway.v1.SkillTextReadResponse - (*SkillManageRequest)(nil), // 84: liveagent.gateway.v1.SkillManageRequest - (*SkillManageResponse)(nil), // 85: liveagent.gateway.v1.SkillManageResponse - (*FileMentionListRequest)(nil), // 86: liveagent.gateway.v1.FileMentionListRequest - (*FileMentionEntry)(nil), // 87: liveagent.gateway.v1.FileMentionEntry - (*FileMentionListResponse)(nil), // 88: liveagent.gateway.v1.FileMentionListResponse - (*FsRoot)(nil), // 89: liveagent.gateway.v1.FsRoot - (*FsRootsRequest)(nil), // 90: liveagent.gateway.v1.FsRootsRequest - (*FsRootsResponse)(nil), // 91: liveagent.gateway.v1.FsRootsResponse - (*FsListDirsRequest)(nil), // 92: liveagent.gateway.v1.FsListDirsRequest - (*FsDirEntry)(nil), // 93: liveagent.gateway.v1.FsDirEntry - (*FsListDirsResponse)(nil), // 94: liveagent.gateway.v1.FsListDirsResponse - (*FsCreateProjectFolderRequest)(nil), // 95: liveagent.gateway.v1.FsCreateProjectFolderRequest - (*FsCreateProjectFolderResponse)(nil), // 96: liveagent.gateway.v1.FsCreateProjectFolderResponse - (*FsListRequest)(nil), // 97: liveagent.gateway.v1.FsListRequest - (*FsListEntry)(nil), // 98: liveagent.gateway.v1.FsListEntry - (*FsListResponse)(nil), // 99: liveagent.gateway.v1.FsListResponse - (*FsReadEditableTextRequest)(nil), // 100: liveagent.gateway.v1.FsReadEditableTextRequest - (*FsReadEditableTextResponse)(nil), // 101: liveagent.gateway.v1.FsReadEditableTextResponse - (*FsReadWorkspaceImageRequest)(nil), // 102: liveagent.gateway.v1.FsReadWorkspaceImageRequest - (*FsReadWorkspaceImageResponse)(nil), // 103: liveagent.gateway.v1.FsReadWorkspaceImageResponse - (*FsWriteTextRequest)(nil), // 104: liveagent.gateway.v1.FsWriteTextRequest - (*FsWriteTextResponse)(nil), // 105: liveagent.gateway.v1.FsWriteTextResponse - (*FsCreateDirRequest)(nil), // 106: liveagent.gateway.v1.FsCreateDirRequest - (*FsCreateDirResponse)(nil), // 107: liveagent.gateway.v1.FsCreateDirResponse - (*FsRenameRequest)(nil), // 108: liveagent.gateway.v1.FsRenameRequest - (*FsRenameResponse)(nil), // 109: liveagent.gateway.v1.FsRenameResponse - (*FsDeleteRequest)(nil), // 110: liveagent.gateway.v1.FsDeleteRequest - (*FsDeleteResponse)(nil), // 111: liveagent.gateway.v1.FsDeleteResponse - (*PingRequest)(nil), // 112: liveagent.gateway.v1.PingRequest - (*PongResponse)(nil), // 113: liveagent.gateway.v1.PongResponse - (*ErrorResponse)(nil), // 114: liveagent.gateway.v1.ErrorResponse + (*HistoryPrefixRequest)(nil), // 52: liveagent.gateway.v1.HistoryPrefixRequest + (*HistoryPrefixResponse)(nil), // 53: liveagent.gateway.v1.HistoryPrefixResponse + (*HistoryRenameRequest)(nil), // 54: liveagent.gateway.v1.HistoryRenameRequest + (*HistoryRenameResponse)(nil), // 55: liveagent.gateway.v1.HistoryRenameResponse + (*HistoryPinRequest)(nil), // 56: liveagent.gateway.v1.HistoryPinRequest + (*HistoryPinResponse)(nil), // 57: liveagent.gateway.v1.HistoryPinResponse + (*HistoryShareStatus)(nil), // 58: liveagent.gateway.v1.HistoryShareStatus + (*HistoryShareGetRequest)(nil), // 59: liveagent.gateway.v1.HistoryShareGetRequest + (*HistoryShareGetResponse)(nil), // 60: liveagent.gateway.v1.HistoryShareGetResponse + (*HistoryShareSetRequest)(nil), // 61: liveagent.gateway.v1.HistoryShareSetRequest + (*HistoryShareSetResponse)(nil), // 62: liveagent.gateway.v1.HistoryShareSetResponse + (*HistoryShareResolveRequest)(nil), // 63: liveagent.gateway.v1.HistoryShareResolveRequest + (*HistoryShareResolveResponse)(nil), // 64: liveagent.gateway.v1.HistoryShareResolveResponse + (*HistoryWorkdirsRequest)(nil), // 65: liveagent.gateway.v1.HistoryWorkdirsRequest + (*HistoryWorkdirSummary)(nil), // 66: liveagent.gateway.v1.HistoryWorkdirSummary + (*HistoryWorkdirsResponse)(nil), // 67: liveagent.gateway.v1.HistoryWorkdirsResponse + (*HistoryDeleteRequest)(nil), // 68: liveagent.gateway.v1.HistoryDeleteRequest + (*HistoryDeleteResponse)(nil), // 69: liveagent.gateway.v1.HistoryDeleteResponse + (*HistorySyncEvent)(nil), // 70: liveagent.gateway.v1.HistorySyncEvent + (*ProviderListRequest)(nil), // 71: liveagent.gateway.v1.ProviderListRequest + (*ProviderListResponse)(nil), // 72: liveagent.gateway.v1.ProviderListResponse + (*SettingsGetRequest)(nil), // 73: liveagent.gateway.v1.SettingsGetRequest + (*SettingsGetResponse)(nil), // 74: liveagent.gateway.v1.SettingsGetResponse + (*SettingsUpdateRequest)(nil), // 75: liveagent.gateway.v1.SettingsUpdateRequest + (*SettingsUpdateResponse)(nil), // 76: liveagent.gateway.v1.SettingsUpdateResponse + (*SettingsResetSshKnownHostRequest)(nil), // 77: liveagent.gateway.v1.SettingsResetSshKnownHostRequest + (*SettingsResetSshKnownHostResponse)(nil), // 78: liveagent.gateway.v1.SettingsResetSshKnownHostResponse + (*SettingsSyncEvent)(nil), // 79: liveagent.gateway.v1.SettingsSyncEvent + (*SkillFilesListRequest)(nil), // 80: liveagent.gateway.v1.SkillFilesListRequest + (*SkillFilesListResponse)(nil), // 81: liveagent.gateway.v1.SkillFilesListResponse + (*SkillMetadataReadRequest)(nil), // 82: liveagent.gateway.v1.SkillMetadataReadRequest + (*SkillMetadataReadResponse)(nil), // 83: liveagent.gateway.v1.SkillMetadataReadResponse + (*SkillTextReadRequest)(nil), // 84: liveagent.gateway.v1.SkillTextReadRequest + (*SkillTextReadResponse)(nil), // 85: liveagent.gateway.v1.SkillTextReadResponse + (*SkillManageRequest)(nil), // 86: liveagent.gateway.v1.SkillManageRequest + (*SkillManageResponse)(nil), // 87: liveagent.gateway.v1.SkillManageResponse + (*FileMentionListRequest)(nil), // 88: liveagent.gateway.v1.FileMentionListRequest + (*FileMentionEntry)(nil), // 89: liveagent.gateway.v1.FileMentionEntry + (*FileMentionListResponse)(nil), // 90: liveagent.gateway.v1.FileMentionListResponse + (*FsRoot)(nil), // 91: liveagent.gateway.v1.FsRoot + (*FsRootsRequest)(nil), // 92: liveagent.gateway.v1.FsRootsRequest + (*FsRootsResponse)(nil), // 93: liveagent.gateway.v1.FsRootsResponse + (*FsListDirsRequest)(nil), // 94: liveagent.gateway.v1.FsListDirsRequest + (*FsDirEntry)(nil), // 95: liveagent.gateway.v1.FsDirEntry + (*FsListDirsResponse)(nil), // 96: liveagent.gateway.v1.FsListDirsResponse + (*FsCreateProjectFolderRequest)(nil), // 97: liveagent.gateway.v1.FsCreateProjectFolderRequest + (*FsCreateProjectFolderResponse)(nil), // 98: liveagent.gateway.v1.FsCreateProjectFolderResponse + (*FsListRequest)(nil), // 99: liveagent.gateway.v1.FsListRequest + (*FsListEntry)(nil), // 100: liveagent.gateway.v1.FsListEntry + (*FsListResponse)(nil), // 101: liveagent.gateway.v1.FsListResponse + (*FsReadEditableTextRequest)(nil), // 102: liveagent.gateway.v1.FsReadEditableTextRequest + (*FsReadEditableTextResponse)(nil), // 103: liveagent.gateway.v1.FsReadEditableTextResponse + (*FsReadWorkspaceImageRequest)(nil), // 104: liveagent.gateway.v1.FsReadWorkspaceImageRequest + (*FsReadWorkspaceImageResponse)(nil), // 105: liveagent.gateway.v1.FsReadWorkspaceImageResponse + (*FsWriteTextRequest)(nil), // 106: liveagent.gateway.v1.FsWriteTextRequest + (*FsWriteTextResponse)(nil), // 107: liveagent.gateway.v1.FsWriteTextResponse + (*FsCreateDirRequest)(nil), // 108: liveagent.gateway.v1.FsCreateDirRequest + (*FsCreateDirResponse)(nil), // 109: liveagent.gateway.v1.FsCreateDirResponse + (*FsRenameRequest)(nil), // 110: liveagent.gateway.v1.FsRenameRequest + (*FsRenameResponse)(nil), // 111: liveagent.gateway.v1.FsRenameResponse + (*FsDeleteRequest)(nil), // 112: liveagent.gateway.v1.FsDeleteRequest + (*FsDeleteResponse)(nil), // 113: liveagent.gateway.v1.FsDeleteResponse + (*PingRequest)(nil), // 114: liveagent.gateway.v1.PingRequest + (*PongResponse)(nil), // 115: liveagent.gateway.v1.PongResponse + (*ErrorResponse)(nil), // 116: liveagent.gateway.v1.ErrorResponse } var file_proto_v1_gateway_proto_depIdxs = []int32{ 41, // 0: liveagent.gateway.v1.GatewayEnvelope.chat_command:type_name -> liveagent.gateway.v1.ChatCommandRequest 45, // 1: liveagent.gateway.v1.GatewayEnvelope.cron_manage:type_name -> liveagent.gateway.v1.CronManageRequest 47, // 2: liveagent.gateway.v1.GatewayEnvelope.history_list:type_name -> liveagent.gateway.v1.HistoryListRequest 50, // 3: liveagent.gateway.v1.GatewayEnvelope.history_get:type_name -> liveagent.gateway.v1.HistoryGetRequest - 52, // 4: liveagent.gateway.v1.GatewayEnvelope.history_rename:type_name -> liveagent.gateway.v1.HistoryRenameRequest - 66, // 5: liveagent.gateway.v1.GatewayEnvelope.history_delete:type_name -> liveagent.gateway.v1.HistoryDeleteRequest - 54, // 6: liveagent.gateway.v1.GatewayEnvelope.history_pin:type_name -> liveagent.gateway.v1.HistoryPinRequest - 57, // 7: liveagent.gateway.v1.GatewayEnvelope.history_share_get:type_name -> liveagent.gateway.v1.HistoryShareGetRequest - 59, // 8: liveagent.gateway.v1.GatewayEnvelope.history_share_set:type_name -> liveagent.gateway.v1.HistoryShareSetRequest - 61, // 9: liveagent.gateway.v1.GatewayEnvelope.history_share_resolve:type_name -> liveagent.gateway.v1.HistoryShareResolveRequest - 63, // 10: liveagent.gateway.v1.GatewayEnvelope.history_workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirsRequest - 69, // 11: liveagent.gateway.v1.GatewayEnvelope.provider_list:type_name -> liveagent.gateway.v1.ProviderListRequest - 71, // 12: liveagent.gateway.v1.GatewayEnvelope.settings_get:type_name -> liveagent.gateway.v1.SettingsGetRequest - 73, // 13: liveagent.gateway.v1.GatewayEnvelope.settings_update:type_name -> liveagent.gateway.v1.SettingsUpdateRequest - 78, // 14: liveagent.gateway.v1.GatewayEnvelope.skill_files_list:type_name -> liveagent.gateway.v1.SkillFilesListRequest - 80, // 15: liveagent.gateway.v1.GatewayEnvelope.skill_metadata_read:type_name -> liveagent.gateway.v1.SkillMetadataReadRequest - 82, // 16: liveagent.gateway.v1.GatewayEnvelope.skill_text_read:type_name -> liveagent.gateway.v1.SkillTextReadRequest - 86, // 17: liveagent.gateway.v1.GatewayEnvelope.file_mention_list:type_name -> liveagent.gateway.v1.FileMentionListRequest - 10, // 18: liveagent.gateway.v1.GatewayEnvelope.upload_readable_files:type_name -> liveagent.gateway.v1.UploadReadableFilesRequest - 90, // 19: liveagent.gateway.v1.GatewayEnvelope.fs_roots:type_name -> liveagent.gateway.v1.FsRootsRequest - 92, // 20: liveagent.gateway.v1.GatewayEnvelope.fs_list_dirs:type_name -> liveagent.gateway.v1.FsListDirsRequest - 112, // 21: liveagent.gateway.v1.GatewayEnvelope.ping:type_name -> liveagent.gateway.v1.PingRequest - 12, // 22: liveagent.gateway.v1.GatewayEnvelope.uploaded_image_preview:type_name -> liveagent.gateway.v1.UploadedImagePreviewRequest - 19, // 23: liveagent.gateway.v1.GatewayEnvelope.memory_manage:type_name -> liveagent.gateway.v1.MemoryManageRequest - 84, // 24: liveagent.gateway.v1.GatewayEnvelope.skill_manage:type_name -> liveagent.gateway.v1.SkillManageRequest - 95, // 25: liveagent.gateway.v1.GatewayEnvelope.fs_create_project_folder:type_name -> liveagent.gateway.v1.FsCreateProjectFolderRequest - 21, // 26: liveagent.gateway.v1.GatewayEnvelope.terminal_request:type_name -> liveagent.gateway.v1.TerminalRequest - 97, // 27: liveagent.gateway.v1.GatewayEnvelope.fs_list:type_name -> liveagent.gateway.v1.FsListRequest - 104, // 28: liveagent.gateway.v1.GatewayEnvelope.fs_write_text:type_name -> liveagent.gateway.v1.FsWriteTextRequest - 106, // 29: liveagent.gateway.v1.GatewayEnvelope.fs_create_dir:type_name -> liveagent.gateway.v1.FsCreateDirRequest - 108, // 30: liveagent.gateway.v1.GatewayEnvelope.fs_rename:type_name -> liveagent.gateway.v1.FsRenameRequest - 110, // 31: liveagent.gateway.v1.GatewayEnvelope.fs_delete:type_name -> liveagent.gateway.v1.FsDeleteRequest - 36, // 32: liveagent.gateway.v1.GatewayEnvelope.git_request:type_name -> liveagent.gateway.v1.GitRequest - 100, // 33: liveagent.gateway.v1.GatewayEnvelope.fs_read_editable_text:type_name -> liveagent.gateway.v1.FsReadEditableTextRequest - 102, // 34: liveagent.gateway.v1.GatewayEnvelope.fs_read_workspace_image:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageRequest - 24, // 35: liveagent.gateway.v1.GatewayEnvelope.sftp_request:type_name -> liveagent.gateway.v1.SftpRequest - 14, // 36: liveagent.gateway.v1.GatewayEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest - 15, // 37: liveagent.gateway.v1.GatewayEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse - 18, // 38: liveagent.gateway.v1.GatewayEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame - 75, // 39: liveagent.gateway.v1.GatewayEnvelope.settings_reset_ssh_known_host:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostRequest - 42, // 40: liveagent.gateway.v1.AgentEnvelope.chat_event:type_name -> liveagent.gateway.v1.ChatEvent - 46, // 41: liveagent.gateway.v1.AgentEnvelope.cron_manage_resp:type_name -> liveagent.gateway.v1.CronManageResponse - 48, // 42: liveagent.gateway.v1.AgentEnvelope.history_list_resp:type_name -> liveagent.gateway.v1.HistoryListResponse - 51, // 43: liveagent.gateway.v1.AgentEnvelope.history_get_resp:type_name -> liveagent.gateway.v1.HistoryGetResponse - 53, // 44: liveagent.gateway.v1.AgentEnvelope.history_rename_resp:type_name -> liveagent.gateway.v1.HistoryRenameResponse - 67, // 45: liveagent.gateway.v1.AgentEnvelope.history_delete_resp:type_name -> liveagent.gateway.v1.HistoryDeleteResponse - 68, // 46: liveagent.gateway.v1.AgentEnvelope.history_sync:type_name -> liveagent.gateway.v1.HistorySyncEvent - 55, // 47: liveagent.gateway.v1.AgentEnvelope.history_pin_resp:type_name -> liveagent.gateway.v1.HistoryPinResponse - 58, // 48: liveagent.gateway.v1.AgentEnvelope.history_share_get_resp:type_name -> liveagent.gateway.v1.HistoryShareGetResponse - 60, // 49: liveagent.gateway.v1.AgentEnvelope.history_share_set_resp:type_name -> liveagent.gateway.v1.HistoryShareSetResponse - 62, // 50: liveagent.gateway.v1.AgentEnvelope.history_share_resolve_resp:type_name -> liveagent.gateway.v1.HistoryShareResolveResponse - 65, // 51: liveagent.gateway.v1.AgentEnvelope.history_workdirs_resp:type_name -> liveagent.gateway.v1.HistoryWorkdirsResponse - 70, // 52: liveagent.gateway.v1.AgentEnvelope.provider_list_resp:type_name -> liveagent.gateway.v1.ProviderListResponse - 72, // 53: liveagent.gateway.v1.AgentEnvelope.settings_get_resp:type_name -> liveagent.gateway.v1.SettingsGetResponse - 74, // 54: liveagent.gateway.v1.AgentEnvelope.settings_update_resp:type_name -> liveagent.gateway.v1.SettingsUpdateResponse - 77, // 55: liveagent.gateway.v1.AgentEnvelope.settings_sync:type_name -> liveagent.gateway.v1.SettingsSyncEvent - 79, // 56: liveagent.gateway.v1.AgentEnvelope.skill_files_list_resp:type_name -> liveagent.gateway.v1.SkillFilesListResponse - 81, // 57: liveagent.gateway.v1.AgentEnvelope.skill_metadata_read_resp:type_name -> liveagent.gateway.v1.SkillMetadataReadResponse - 83, // 58: liveagent.gateway.v1.AgentEnvelope.skill_text_read_resp:type_name -> liveagent.gateway.v1.SkillTextReadResponse - 88, // 59: liveagent.gateway.v1.AgentEnvelope.file_mention_list_resp:type_name -> liveagent.gateway.v1.FileMentionListResponse - 11, // 60: liveagent.gateway.v1.AgentEnvelope.upload_readable_files_resp:type_name -> liveagent.gateway.v1.UploadReadableFilesResponse - 91, // 61: liveagent.gateway.v1.AgentEnvelope.fs_roots_resp:type_name -> liveagent.gateway.v1.FsRootsResponse - 113, // 62: liveagent.gateway.v1.AgentEnvelope.pong:type_name -> liveagent.gateway.v1.PongResponse - 94, // 63: liveagent.gateway.v1.AgentEnvelope.fs_list_dirs_resp:type_name -> liveagent.gateway.v1.FsListDirsResponse - 13, // 64: liveagent.gateway.v1.AgentEnvelope.uploaded_image_preview_resp:type_name -> liveagent.gateway.v1.UploadedImagePreviewResponse - 20, // 65: liveagent.gateway.v1.AgentEnvelope.memory_manage_resp:type_name -> liveagent.gateway.v1.MemoryManageResponse - 85, // 66: liveagent.gateway.v1.AgentEnvelope.skill_manage_resp:type_name -> liveagent.gateway.v1.SkillManageResponse - 96, // 67: liveagent.gateway.v1.AgentEnvelope.fs_create_project_folder_resp:type_name -> liveagent.gateway.v1.FsCreateProjectFolderResponse - 33, // 68: liveagent.gateway.v1.AgentEnvelope.terminal_response:type_name -> liveagent.gateway.v1.TerminalResponse - 34, // 69: liveagent.gateway.v1.AgentEnvelope.terminal_event:type_name -> liveagent.gateway.v1.TerminalEvent - 99, // 70: liveagent.gateway.v1.AgentEnvelope.fs_list_resp:type_name -> liveagent.gateway.v1.FsListResponse - 105, // 71: liveagent.gateway.v1.AgentEnvelope.fs_write_text_resp:type_name -> liveagent.gateway.v1.FsWriteTextResponse - 107, // 72: liveagent.gateway.v1.AgentEnvelope.fs_create_dir_resp:type_name -> liveagent.gateway.v1.FsCreateDirResponse - 109, // 73: liveagent.gateway.v1.AgentEnvelope.fs_rename_resp:type_name -> liveagent.gateway.v1.FsRenameResponse - 111, // 74: liveagent.gateway.v1.AgentEnvelope.fs_delete_resp:type_name -> liveagent.gateway.v1.FsDeleteResponse - 37, // 75: liveagent.gateway.v1.AgentEnvelope.git_response:type_name -> liveagent.gateway.v1.GitResponse - 101, // 76: liveagent.gateway.v1.AgentEnvelope.fs_read_editable_text_resp:type_name -> liveagent.gateway.v1.FsReadEditableTextResponse - 103, // 77: liveagent.gateway.v1.AgentEnvelope.fs_read_workspace_image_resp:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageResponse - 27, // 78: liveagent.gateway.v1.AgentEnvelope.sftp_response:type_name -> liveagent.gateway.v1.SftpResponse - 28, // 79: liveagent.gateway.v1.AgentEnvelope.sftp_event:type_name -> liveagent.gateway.v1.SftpEvent - 14, // 80: liveagent.gateway.v1.AgentEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest - 15, // 81: liveagent.gateway.v1.AgentEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse - 18, // 82: liveagent.gateway.v1.AgentEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame - 43, // 83: liveagent.gateway.v1.AgentEnvelope.chat_control:type_name -> liveagent.gateway.v1.ChatControlEvent - 44, // 84: liveagent.gateway.v1.AgentEnvelope.runtime_status:type_name -> liveagent.gateway.v1.RuntimeStatusEvent - 76, // 85: liveagent.gateway.v1.AgentEnvelope.settings_reset_ssh_known_host_resp:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostResponse - 114, // 86: liveagent.gateway.v1.AgentEnvelope.error:type_name -> liveagent.gateway.v1.ErrorResponse - 9, // 87: liveagent.gateway.v1.UploadReadableFilesRequest.files:type_name -> liveagent.gateway.v1.UploadReadableFile - 8, // 88: liveagent.gateway.v1.UploadReadableFilesResponse.files:type_name -> liveagent.gateway.v1.ChatUploadedFile - 16, // 89: liveagent.gateway.v1.TunnelControlResponse.tunnels:type_name -> liveagent.gateway.v1.TunnelSummary - 16, // 90: liveagent.gateway.v1.TunnelControlResponse.tunnel:type_name -> liveagent.gateway.v1.TunnelSummary - 0, // 91: liveagent.gateway.v1.TunnelFrame.kind:type_name -> liveagent.gateway.v1.TunnelFrameKind - 17, // 92: liveagent.gateway.v1.TunnelFrame.headers:type_name -> liveagent.gateway.v1.TunnelHeader - 23, // 93: liveagent.gateway.v1.TerminalSession.ssh:type_name -> liveagent.gateway.v1.TerminalSshMetadata - 25, // 94: liveagent.gateway.v1.SftpResponse.entries:type_name -> liveagent.gateway.v1.SftpEntry - 25, // 95: liveagent.gateway.v1.SftpResponse.entry:type_name -> liveagent.gateway.v1.SftpEntry - 26, // 96: liveagent.gateway.v1.SftpResponse.transfer:type_name -> liveagent.gateway.v1.SftpTransfer - 26, // 97: liveagent.gateway.v1.SftpEvent.transfer:type_name -> liveagent.gateway.v1.SftpTransfer - 31, // 98: liveagent.gateway.v1.TerminalSshTabsSnapshot.tabs:type_name -> liveagent.gateway.v1.TerminalSshTab - 22, // 99: liveagent.gateway.v1.TerminalResponse.sessions:type_name -> liveagent.gateway.v1.TerminalSession - 22, // 100: liveagent.gateway.v1.TerminalResponse.session:type_name -> liveagent.gateway.v1.TerminalSession - 30, // 101: liveagent.gateway.v1.TerminalResponse.shell_options:type_name -> liveagent.gateway.v1.TerminalShellOption - 29, // 102: liveagent.gateway.v1.TerminalResponse.ssh_prompt:type_name -> liveagent.gateway.v1.TerminalSshPrompt - 32, // 103: liveagent.gateway.v1.TerminalResponse.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot - 22, // 104: liveagent.gateway.v1.TerminalEvent.session:type_name -> liveagent.gateway.v1.TerminalSession - 32, // 105: liveagent.gateway.v1.TerminalEvent.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot - 22, // 106: liveagent.gateway.v1.TerminalStreamFrame.session:type_name -> liveagent.gateway.v1.TerminalSession - 6, // 107: liveagent.gateway.v1.ChatRequest.selected_model:type_name -> liveagent.gateway.v1.ChatSelectedModel - 8, // 108: liveagent.gateway.v1.ChatRequest.uploaded_files:type_name -> liveagent.gateway.v1.ChatUploadedFile - 7, // 109: liveagent.gateway.v1.ChatRequest.runtime_controls:type_name -> liveagent.gateway.v1.ChatRuntimeControls - 38, // 110: liveagent.gateway.v1.ChatCommandRequest.request:type_name -> liveagent.gateway.v1.ChatRequest - 39, // 111: liveagent.gateway.v1.ChatCommandRequest.base_message_ref:type_name -> liveagent.gateway.v1.ChatMessageRef - 40, // 112: liveagent.gateway.v1.ChatCommandRequest.cancel:type_name -> liveagent.gateway.v1.CancelChatRequest - 1, // 113: liveagent.gateway.v1.ChatEvent.type:type_name -> liveagent.gateway.v1.ChatEvent.ChatEventType - 49, // 114: liveagent.gateway.v1.HistoryListResponse.conversations:type_name -> liveagent.gateway.v1.ConversationSummary - 49, // 115: liveagent.gateway.v1.HistoryGetResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 49, // 116: liveagent.gateway.v1.HistoryRenameResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 49, // 117: liveagent.gateway.v1.HistoryPinResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 56, // 118: liveagent.gateway.v1.HistoryShareGetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus - 56, // 119: liveagent.gateway.v1.HistoryShareSetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus - 49, // 120: liveagent.gateway.v1.HistoryShareResolveResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 64, // 121: liveagent.gateway.v1.HistoryWorkdirsResponse.workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirSummary - 49, // 122: liveagent.gateway.v1.HistorySyncEvent.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 87, // 123: liveagent.gateway.v1.FileMentionListResponse.entries:type_name -> liveagent.gateway.v1.FileMentionEntry - 89, // 124: liveagent.gateway.v1.FsRootsResponse.roots:type_name -> liveagent.gateway.v1.FsRoot - 93, // 125: liveagent.gateway.v1.FsListDirsResponse.entries:type_name -> liveagent.gateway.v1.FsDirEntry - 98, // 126: liveagent.gateway.v1.FsListResponse.entries:type_name -> liveagent.gateway.v1.FsListEntry - 5, // 127: liveagent.gateway.v1.AgentGateway.AgentConnect:input_type -> liveagent.gateway.v1.AgentEnvelope - 35, // 128: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:input_type -> liveagent.gateway.v1.TerminalStreamFrame - 2, // 129: liveagent.gateway.v1.AgentGateway.Authenticate:input_type -> liveagent.gateway.v1.AuthRequest - 4, // 130: liveagent.gateway.v1.AgentGateway.AgentConnect:output_type -> liveagent.gateway.v1.GatewayEnvelope - 35, // 131: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:output_type -> liveagent.gateway.v1.TerminalStreamFrame - 3, // 132: liveagent.gateway.v1.AgentGateway.Authenticate:output_type -> liveagent.gateway.v1.AuthResponse - 130, // [130:133] is the sub-list for method output_type - 127, // [127:130] is the sub-list for method input_type - 127, // [127:127] is the sub-list for extension type_name - 127, // [127:127] is the sub-list for extension extendee - 0, // [0:127] is the sub-list for field type_name + 54, // 4: liveagent.gateway.v1.GatewayEnvelope.history_rename:type_name -> liveagent.gateway.v1.HistoryRenameRequest + 68, // 5: liveagent.gateway.v1.GatewayEnvelope.history_delete:type_name -> liveagent.gateway.v1.HistoryDeleteRequest + 52, // 6: liveagent.gateway.v1.GatewayEnvelope.history_prefix:type_name -> liveagent.gateway.v1.HistoryPrefixRequest + 56, // 7: liveagent.gateway.v1.GatewayEnvelope.history_pin:type_name -> liveagent.gateway.v1.HistoryPinRequest + 59, // 8: liveagent.gateway.v1.GatewayEnvelope.history_share_get:type_name -> liveagent.gateway.v1.HistoryShareGetRequest + 61, // 9: liveagent.gateway.v1.GatewayEnvelope.history_share_set:type_name -> liveagent.gateway.v1.HistoryShareSetRequest + 63, // 10: liveagent.gateway.v1.GatewayEnvelope.history_share_resolve:type_name -> liveagent.gateway.v1.HistoryShareResolveRequest + 65, // 11: liveagent.gateway.v1.GatewayEnvelope.history_workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirsRequest + 71, // 12: liveagent.gateway.v1.GatewayEnvelope.provider_list:type_name -> liveagent.gateway.v1.ProviderListRequest + 73, // 13: liveagent.gateway.v1.GatewayEnvelope.settings_get:type_name -> liveagent.gateway.v1.SettingsGetRequest + 75, // 14: liveagent.gateway.v1.GatewayEnvelope.settings_update:type_name -> liveagent.gateway.v1.SettingsUpdateRequest + 80, // 15: liveagent.gateway.v1.GatewayEnvelope.skill_files_list:type_name -> liveagent.gateway.v1.SkillFilesListRequest + 82, // 16: liveagent.gateway.v1.GatewayEnvelope.skill_metadata_read:type_name -> liveagent.gateway.v1.SkillMetadataReadRequest + 84, // 17: liveagent.gateway.v1.GatewayEnvelope.skill_text_read:type_name -> liveagent.gateway.v1.SkillTextReadRequest + 88, // 18: liveagent.gateway.v1.GatewayEnvelope.file_mention_list:type_name -> liveagent.gateway.v1.FileMentionListRequest + 10, // 19: liveagent.gateway.v1.GatewayEnvelope.upload_readable_files:type_name -> liveagent.gateway.v1.UploadReadableFilesRequest + 92, // 20: liveagent.gateway.v1.GatewayEnvelope.fs_roots:type_name -> liveagent.gateway.v1.FsRootsRequest + 94, // 21: liveagent.gateway.v1.GatewayEnvelope.fs_list_dirs:type_name -> liveagent.gateway.v1.FsListDirsRequest + 114, // 22: liveagent.gateway.v1.GatewayEnvelope.ping:type_name -> liveagent.gateway.v1.PingRequest + 12, // 23: liveagent.gateway.v1.GatewayEnvelope.uploaded_image_preview:type_name -> liveagent.gateway.v1.UploadedImagePreviewRequest + 19, // 24: liveagent.gateway.v1.GatewayEnvelope.memory_manage:type_name -> liveagent.gateway.v1.MemoryManageRequest + 86, // 25: liveagent.gateway.v1.GatewayEnvelope.skill_manage:type_name -> liveagent.gateway.v1.SkillManageRequest + 97, // 26: liveagent.gateway.v1.GatewayEnvelope.fs_create_project_folder:type_name -> liveagent.gateway.v1.FsCreateProjectFolderRequest + 21, // 27: liveagent.gateway.v1.GatewayEnvelope.terminal_request:type_name -> liveagent.gateway.v1.TerminalRequest + 99, // 28: liveagent.gateway.v1.GatewayEnvelope.fs_list:type_name -> liveagent.gateway.v1.FsListRequest + 106, // 29: liveagent.gateway.v1.GatewayEnvelope.fs_write_text:type_name -> liveagent.gateway.v1.FsWriteTextRequest + 108, // 30: liveagent.gateway.v1.GatewayEnvelope.fs_create_dir:type_name -> liveagent.gateway.v1.FsCreateDirRequest + 110, // 31: liveagent.gateway.v1.GatewayEnvelope.fs_rename:type_name -> liveagent.gateway.v1.FsRenameRequest + 112, // 32: liveagent.gateway.v1.GatewayEnvelope.fs_delete:type_name -> liveagent.gateway.v1.FsDeleteRequest + 36, // 33: liveagent.gateway.v1.GatewayEnvelope.git_request:type_name -> liveagent.gateway.v1.GitRequest + 102, // 34: liveagent.gateway.v1.GatewayEnvelope.fs_read_editable_text:type_name -> liveagent.gateway.v1.FsReadEditableTextRequest + 104, // 35: liveagent.gateway.v1.GatewayEnvelope.fs_read_workspace_image:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageRequest + 24, // 36: liveagent.gateway.v1.GatewayEnvelope.sftp_request:type_name -> liveagent.gateway.v1.SftpRequest + 14, // 37: liveagent.gateway.v1.GatewayEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest + 15, // 38: liveagent.gateway.v1.GatewayEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse + 18, // 39: liveagent.gateway.v1.GatewayEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame + 77, // 40: liveagent.gateway.v1.GatewayEnvelope.settings_reset_ssh_known_host:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostRequest + 42, // 41: liveagent.gateway.v1.AgentEnvelope.chat_event:type_name -> liveagent.gateway.v1.ChatEvent + 46, // 42: liveagent.gateway.v1.AgentEnvelope.cron_manage_resp:type_name -> liveagent.gateway.v1.CronManageResponse + 48, // 43: liveagent.gateway.v1.AgentEnvelope.history_list_resp:type_name -> liveagent.gateway.v1.HistoryListResponse + 51, // 44: liveagent.gateway.v1.AgentEnvelope.history_get_resp:type_name -> liveagent.gateway.v1.HistoryGetResponse + 55, // 45: liveagent.gateway.v1.AgentEnvelope.history_rename_resp:type_name -> liveagent.gateway.v1.HistoryRenameResponse + 69, // 46: liveagent.gateway.v1.AgentEnvelope.history_delete_resp:type_name -> liveagent.gateway.v1.HistoryDeleteResponse + 70, // 47: liveagent.gateway.v1.AgentEnvelope.history_sync:type_name -> liveagent.gateway.v1.HistorySyncEvent + 53, // 48: liveagent.gateway.v1.AgentEnvelope.history_prefix_resp:type_name -> liveagent.gateway.v1.HistoryPrefixResponse + 57, // 49: liveagent.gateway.v1.AgentEnvelope.history_pin_resp:type_name -> liveagent.gateway.v1.HistoryPinResponse + 60, // 50: liveagent.gateway.v1.AgentEnvelope.history_share_get_resp:type_name -> liveagent.gateway.v1.HistoryShareGetResponse + 62, // 51: liveagent.gateway.v1.AgentEnvelope.history_share_set_resp:type_name -> liveagent.gateway.v1.HistoryShareSetResponse + 64, // 52: liveagent.gateway.v1.AgentEnvelope.history_share_resolve_resp:type_name -> liveagent.gateway.v1.HistoryShareResolveResponse + 67, // 53: liveagent.gateway.v1.AgentEnvelope.history_workdirs_resp:type_name -> liveagent.gateway.v1.HistoryWorkdirsResponse + 72, // 54: liveagent.gateway.v1.AgentEnvelope.provider_list_resp:type_name -> liveagent.gateway.v1.ProviderListResponse + 74, // 55: liveagent.gateway.v1.AgentEnvelope.settings_get_resp:type_name -> liveagent.gateway.v1.SettingsGetResponse + 76, // 56: liveagent.gateway.v1.AgentEnvelope.settings_update_resp:type_name -> liveagent.gateway.v1.SettingsUpdateResponse + 79, // 57: liveagent.gateway.v1.AgentEnvelope.settings_sync:type_name -> liveagent.gateway.v1.SettingsSyncEvent + 81, // 58: liveagent.gateway.v1.AgentEnvelope.skill_files_list_resp:type_name -> liveagent.gateway.v1.SkillFilesListResponse + 83, // 59: liveagent.gateway.v1.AgentEnvelope.skill_metadata_read_resp:type_name -> liveagent.gateway.v1.SkillMetadataReadResponse + 85, // 60: liveagent.gateway.v1.AgentEnvelope.skill_text_read_resp:type_name -> liveagent.gateway.v1.SkillTextReadResponse + 90, // 61: liveagent.gateway.v1.AgentEnvelope.file_mention_list_resp:type_name -> liveagent.gateway.v1.FileMentionListResponse + 11, // 62: liveagent.gateway.v1.AgentEnvelope.upload_readable_files_resp:type_name -> liveagent.gateway.v1.UploadReadableFilesResponse + 93, // 63: liveagent.gateway.v1.AgentEnvelope.fs_roots_resp:type_name -> liveagent.gateway.v1.FsRootsResponse + 115, // 64: liveagent.gateway.v1.AgentEnvelope.pong:type_name -> liveagent.gateway.v1.PongResponse + 96, // 65: liveagent.gateway.v1.AgentEnvelope.fs_list_dirs_resp:type_name -> liveagent.gateway.v1.FsListDirsResponse + 13, // 66: liveagent.gateway.v1.AgentEnvelope.uploaded_image_preview_resp:type_name -> liveagent.gateway.v1.UploadedImagePreviewResponse + 20, // 67: liveagent.gateway.v1.AgentEnvelope.memory_manage_resp:type_name -> liveagent.gateway.v1.MemoryManageResponse + 87, // 68: liveagent.gateway.v1.AgentEnvelope.skill_manage_resp:type_name -> liveagent.gateway.v1.SkillManageResponse + 98, // 69: liveagent.gateway.v1.AgentEnvelope.fs_create_project_folder_resp:type_name -> liveagent.gateway.v1.FsCreateProjectFolderResponse + 33, // 70: liveagent.gateway.v1.AgentEnvelope.terminal_response:type_name -> liveagent.gateway.v1.TerminalResponse + 34, // 71: liveagent.gateway.v1.AgentEnvelope.terminal_event:type_name -> liveagent.gateway.v1.TerminalEvent + 101, // 72: liveagent.gateway.v1.AgentEnvelope.fs_list_resp:type_name -> liveagent.gateway.v1.FsListResponse + 107, // 73: liveagent.gateway.v1.AgentEnvelope.fs_write_text_resp:type_name -> liveagent.gateway.v1.FsWriteTextResponse + 109, // 74: liveagent.gateway.v1.AgentEnvelope.fs_create_dir_resp:type_name -> liveagent.gateway.v1.FsCreateDirResponse + 111, // 75: liveagent.gateway.v1.AgentEnvelope.fs_rename_resp:type_name -> liveagent.gateway.v1.FsRenameResponse + 113, // 76: liveagent.gateway.v1.AgentEnvelope.fs_delete_resp:type_name -> liveagent.gateway.v1.FsDeleteResponse + 37, // 77: liveagent.gateway.v1.AgentEnvelope.git_response:type_name -> liveagent.gateway.v1.GitResponse + 103, // 78: liveagent.gateway.v1.AgentEnvelope.fs_read_editable_text_resp:type_name -> liveagent.gateway.v1.FsReadEditableTextResponse + 105, // 79: liveagent.gateway.v1.AgentEnvelope.fs_read_workspace_image_resp:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageResponse + 27, // 80: liveagent.gateway.v1.AgentEnvelope.sftp_response:type_name -> liveagent.gateway.v1.SftpResponse + 28, // 81: liveagent.gateway.v1.AgentEnvelope.sftp_event:type_name -> liveagent.gateway.v1.SftpEvent + 14, // 82: liveagent.gateway.v1.AgentEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest + 15, // 83: liveagent.gateway.v1.AgentEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse + 18, // 84: liveagent.gateway.v1.AgentEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame + 43, // 85: liveagent.gateway.v1.AgentEnvelope.chat_control:type_name -> liveagent.gateway.v1.ChatControlEvent + 44, // 86: liveagent.gateway.v1.AgentEnvelope.runtime_status:type_name -> liveagent.gateway.v1.RuntimeStatusEvent + 78, // 87: liveagent.gateway.v1.AgentEnvelope.settings_reset_ssh_known_host_resp:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostResponse + 116, // 88: liveagent.gateway.v1.AgentEnvelope.error:type_name -> liveagent.gateway.v1.ErrorResponse + 9, // 89: liveagent.gateway.v1.UploadReadableFilesRequest.files:type_name -> liveagent.gateway.v1.UploadReadableFile + 8, // 90: liveagent.gateway.v1.UploadReadableFilesResponse.files:type_name -> liveagent.gateway.v1.ChatUploadedFile + 16, // 91: liveagent.gateway.v1.TunnelControlResponse.tunnels:type_name -> liveagent.gateway.v1.TunnelSummary + 16, // 92: liveagent.gateway.v1.TunnelControlResponse.tunnel:type_name -> liveagent.gateway.v1.TunnelSummary + 0, // 93: liveagent.gateway.v1.TunnelFrame.kind:type_name -> liveagent.gateway.v1.TunnelFrameKind + 17, // 94: liveagent.gateway.v1.TunnelFrame.headers:type_name -> liveagent.gateway.v1.TunnelHeader + 23, // 95: liveagent.gateway.v1.TerminalSession.ssh:type_name -> liveagent.gateway.v1.TerminalSshMetadata + 25, // 96: liveagent.gateway.v1.SftpResponse.entries:type_name -> liveagent.gateway.v1.SftpEntry + 25, // 97: liveagent.gateway.v1.SftpResponse.entry:type_name -> liveagent.gateway.v1.SftpEntry + 26, // 98: liveagent.gateway.v1.SftpResponse.transfer:type_name -> liveagent.gateway.v1.SftpTransfer + 26, // 99: liveagent.gateway.v1.SftpEvent.transfer:type_name -> liveagent.gateway.v1.SftpTransfer + 31, // 100: liveagent.gateway.v1.TerminalSshTabsSnapshot.tabs:type_name -> liveagent.gateway.v1.TerminalSshTab + 22, // 101: liveagent.gateway.v1.TerminalResponse.sessions:type_name -> liveagent.gateway.v1.TerminalSession + 22, // 102: liveagent.gateway.v1.TerminalResponse.session:type_name -> liveagent.gateway.v1.TerminalSession + 30, // 103: liveagent.gateway.v1.TerminalResponse.shell_options:type_name -> liveagent.gateway.v1.TerminalShellOption + 29, // 104: liveagent.gateway.v1.TerminalResponse.ssh_prompt:type_name -> liveagent.gateway.v1.TerminalSshPrompt + 32, // 105: liveagent.gateway.v1.TerminalResponse.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot + 22, // 106: liveagent.gateway.v1.TerminalEvent.session:type_name -> liveagent.gateway.v1.TerminalSession + 32, // 107: liveagent.gateway.v1.TerminalEvent.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot + 22, // 108: liveagent.gateway.v1.TerminalStreamFrame.session:type_name -> liveagent.gateway.v1.TerminalSession + 6, // 109: liveagent.gateway.v1.ChatRequest.selected_model:type_name -> liveagent.gateway.v1.ChatSelectedModel + 8, // 110: liveagent.gateway.v1.ChatRequest.uploaded_files:type_name -> liveagent.gateway.v1.ChatUploadedFile + 7, // 111: liveagent.gateway.v1.ChatRequest.runtime_controls:type_name -> liveagent.gateway.v1.ChatRuntimeControls + 38, // 112: liveagent.gateway.v1.ChatCommandRequest.request:type_name -> liveagent.gateway.v1.ChatRequest + 39, // 113: liveagent.gateway.v1.ChatCommandRequest.base_message_ref:type_name -> liveagent.gateway.v1.ChatMessageRef + 40, // 114: liveagent.gateway.v1.ChatCommandRequest.cancel:type_name -> liveagent.gateway.v1.CancelChatRequest + 1, // 115: liveagent.gateway.v1.ChatEvent.type:type_name -> liveagent.gateway.v1.ChatEvent.ChatEventType + 49, // 116: liveagent.gateway.v1.HistoryListResponse.conversations:type_name -> liveagent.gateway.v1.ConversationSummary + 49, // 117: liveagent.gateway.v1.HistoryGetResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 39, // 118: liveagent.gateway.v1.HistoryPrefixRequest.base_message_ref:type_name -> liveagent.gateway.v1.ChatMessageRef + 49, // 119: liveagent.gateway.v1.HistoryPrefixResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 49, // 120: liveagent.gateway.v1.HistoryRenameResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 49, // 121: liveagent.gateway.v1.HistoryPinResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 58, // 122: liveagent.gateway.v1.HistoryShareGetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus + 58, // 123: liveagent.gateway.v1.HistoryShareSetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus + 49, // 124: liveagent.gateway.v1.HistoryShareResolveResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 66, // 125: liveagent.gateway.v1.HistoryWorkdirsResponse.workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirSummary + 49, // 126: liveagent.gateway.v1.HistorySyncEvent.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 89, // 127: liveagent.gateway.v1.FileMentionListResponse.entries:type_name -> liveagent.gateway.v1.FileMentionEntry + 91, // 128: liveagent.gateway.v1.FsRootsResponse.roots:type_name -> liveagent.gateway.v1.FsRoot + 95, // 129: liveagent.gateway.v1.FsListDirsResponse.entries:type_name -> liveagent.gateway.v1.FsDirEntry + 100, // 130: liveagent.gateway.v1.FsListResponse.entries:type_name -> liveagent.gateway.v1.FsListEntry + 5, // 131: liveagent.gateway.v1.AgentGateway.AgentConnect:input_type -> liveagent.gateway.v1.AgentEnvelope + 35, // 132: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:input_type -> liveagent.gateway.v1.TerminalStreamFrame + 2, // 133: liveagent.gateway.v1.AgentGateway.Authenticate:input_type -> liveagent.gateway.v1.AuthRequest + 4, // 134: liveagent.gateway.v1.AgentGateway.AgentConnect:output_type -> liveagent.gateway.v1.GatewayEnvelope + 35, // 135: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:output_type -> liveagent.gateway.v1.TerminalStreamFrame + 3, // 136: liveagent.gateway.v1.AgentGateway.Authenticate:output_type -> liveagent.gateway.v1.AuthResponse + 134, // [134:137] is the sub-list for method output_type + 131, // [131:134] is the sub-list for method input_type + 131, // [131:131] is the sub-list for extension type_name + 131, // [131:131] is the sub-list for extension extendee + 0, // [0:131] is the sub-list for field type_name } func init() { file_proto_v1_gateway_proto_init() } @@ -10017,6 +10250,7 @@ func file_proto_v1_gateway_proto_init() { (*GatewayEnvelope_HistoryGet)(nil), (*GatewayEnvelope_HistoryRename)(nil), (*GatewayEnvelope_HistoryDelete)(nil), + (*GatewayEnvelope_HistoryPrefix)(nil), (*GatewayEnvelope_HistoryPin)(nil), (*GatewayEnvelope_HistoryShareGet)(nil), (*GatewayEnvelope_HistoryShareSet)(nil), @@ -10060,6 +10294,7 @@ func file_proto_v1_gateway_proto_init() { (*AgentEnvelope_HistoryRenameResp)(nil), (*AgentEnvelope_HistoryDeleteResp)(nil), (*AgentEnvelope_HistorySync)(nil), + (*AgentEnvelope_HistoryPrefixResp)(nil), (*AgentEnvelope_HistoryPinResp)(nil), (*AgentEnvelope_HistoryShareGetResp)(nil), (*AgentEnvelope_HistoryShareSetResp)(nil), @@ -10101,14 +10336,14 @@ func file_proto_v1_gateway_proto_init() { (*AgentEnvelope_SettingsResetSshKnownHostResp)(nil), (*AgentEnvelope_Error)(nil), } - file_proto_v1_gateway_proto_msgTypes[57].OneofWrappers = []any{} + file_proto_v1_gateway_proto_msgTypes[59].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_v1_gateway_proto_rawDesc), len(file_proto_v1_gateway_proto_rawDesc)), NumEnums: 2, - NumMessages: 113, + NumMessages: 115, NumExtensions: 0, NumServices: 1, }, diff --git a/crates/agent-gateway/internal/server/chat_commands.go b/crates/agent-gateway/internal/server/chat_commands.go index 0419c6f3..f9f17f50 100644 --- a/crates/agent-gateway/internal/server/chat_commands.go +++ b/crates/agent-gateway/internal/server/chat_commands.go @@ -18,8 +18,12 @@ import ( ) type chatCommandMessageRef struct { - SegmentIndex int `json:"segment_index"` - MessageIndex int `json:"message_index"` + SegmentIndex int `json:"segment_index"` + MessageIndex int `json:"message_index"` + SegmentID string `json:"segment_id"` + MessageID string `json:"message_id"` + Role string `json:"role"` + ContentHash string `json:"content_hash"` } type chatCommandStart struct { @@ -291,6 +295,10 @@ func buildProtoChatMessageRef(ref *chatCommandMessageRef) *gatewayv1.ChatMessage return &gatewayv1.ChatMessageRef{ SegmentIndex: int32(ref.SegmentIndex), MessageIndex: int32(ref.MessageIndex), + SegmentId: strings.TrimSpace(ref.SegmentID), + MessageId: strings.TrimSpace(ref.MessageID), + Role: strings.TrimSpace(ref.Role), + ContentHash: strings.TrimSpace(ref.ContentHash), } } @@ -301,6 +309,16 @@ func validateChatMessageRef(ref *chatCommandMessageRef) error { if ref.SegmentIndex < 0 || ref.MessageIndex < 0 { return errors.New("base_message_ref indexes must be non-negative") } + ref.SegmentID = strings.TrimSpace(ref.SegmentID) + ref.MessageID = strings.TrimSpace(ref.MessageID) + ref.Role = strings.TrimSpace(ref.Role) + ref.ContentHash = strings.TrimSpace(ref.ContentHash) + if ref.SegmentID == "" || ref.MessageID == "" || ref.Role == "" || ref.ContentHash == "" { + return errors.New("base_message_ref requires segment_id, message_id, role, and content_hash") + } + if ref.Role != "user" { + return errors.New("base_message_ref role must be user") + } return nil } diff --git a/crates/agent-gateway/internal/server/http_test.go b/crates/agent-gateway/internal/server/http_test.go index b4a4494f..c2cf3c91 100644 --- a/crates/agent-gateway/internal/server/http_test.go +++ b/crates/agent-gateway/internal/server/http_test.go @@ -636,7 +636,7 @@ func TestChatEditResendRejectsNegativeBaseMessageRef(t *testing.T) { req := httptest.NewRequest( http.MethodPost, "http://gateway.test/api/chat/commands", - strings.NewReader(`{"type":"chat.edit_resend","payload":{"message":"edited","conversation_id":"conversation-1","client_request_id":"client-edit-1","base_message_ref":{"segment_index":-1,"message_index":4}}}`), + strings.NewReader(`{"type":"chat.edit_resend","payload":{"message":"edited","conversation_id":"conversation-1","client_request_id":"client-edit-1","base_message_ref":{"segment_index":-1,"message_index":4,"segment_id":"segment-a","message_id":"user-a","role":"user","content_hash":"fnv1a32:00000000"}}}`), ) req.Header.Set("Authorization", "Bearer dev-token") req.Header.Set("Content-Type", "application/json") @@ -949,7 +949,7 @@ func TestChatCommandSeqContinuesAcrossConversationRuns(t *testing.T) { } sm.MarkChatRunControl(firstRunID, "conversation-1", "completed", "", "") - second := postCommand(`{"type":"chat.edit_resend","payload":{"message":"edited","conversation_id":"conversation-1","client_request_id":"client-edit-1","base_message_ref":{"segment_index":0,"message_index":0}}}`) + second := postCommand(`{"type":"chat.edit_resend","payload":{"message":"edited","conversation_id":"conversation-1","client_request_id":"client-edit-1","base_message_ref":{"segment_index":0,"message_index":0,"segment_id":"segment-a","message_id":"user-a","role":"user","content_hash":"fnv1a32:00000000"}}}`) secondRunID, _ := second["run_id"].(string) if secondRunID == "" || secondRunID == firstRunID || second["accepted_seq"] != float64(4) { t.Fatalf("second response = %#v, want new run accepted_seq 4", second) @@ -1048,7 +1048,7 @@ func TestChatEditResendSendsSingleChatCommand(t *testing.T) { req, err := http.NewRequest( http.MethodPost, ts.URL+"/api/chat/commands", - strings.NewReader(`{"type":"chat.edit_resend","payload":{"message":"edited","conversation_id":"conversation-1","client_request_id":"client-edit-1","base_message_ref":{"segment_index":2,"message_index":4}}}`), + strings.NewReader(`{"type":"chat.edit_resend","payload":{"message":"edited","conversation_id":"conversation-1","client_request_id":"client-edit-1","base_message_ref":{"segment_index":2,"message_index":4,"segment_id":"segment-c","message_id":"user-c","role":"user","content_hash":"fnv1a32:00000000"}}}`), ) if err != nil { t.Fatal(err) @@ -1130,7 +1130,11 @@ func TestChatEditResendSendsSingleChatCommand(t *testing.T) { chatReq.GetConversationId() != "conversation-1" || chatReq.GetClientRequestId() != "client-edit-1" || baseRef.GetSegmentIndex() != 2 || - baseRef.GetMessageIndex() != 4 { + baseRef.GetMessageIndex() != 4 || + baseRef.GetSegmentId() != "segment-c" || + baseRef.GetMessageId() != "user-c" || + baseRef.GetRole() != "user" || + baseRef.GetContentHash() != "fnv1a32:00000000" { t.Fatalf("chat command id=%q command=%#v", outbound.GetRequestId(), command) } case <-time.After(time.Second): diff --git a/crates/agent-gateway/internal/server/websocket_history_handlers.go b/crates/agent-gateway/internal/server/websocket_history_handlers.go index 26c61a9f..7ac6e67e 100644 --- a/crates/agent-gateway/internal/server/websocket_history_handlers.go +++ b/crates/agent-gateway/internal/server/websocket_history_handlers.go @@ -238,6 +238,68 @@ func (c *websocketConnection) handleHistoryGet(req websocketRequest) { }) } +func (c *websocketConnection) handleHistoryPrefix(req websocketRequest) { + type payload struct { + ConversationID string `json:"conversation_id"` + MaxMessages int32 `json:"max_messages"` + BaseMessageRef *chatCommandMessageRef `json:"base_message_ref"` + } + + var body payload + if err := decodeWebSocketPayload(req.Payload, &body); err != nil { + _ = c.writeError(req.ID, "invalid history.prefix payload") + return + } + conversationID, err := requireTrimmedWebSocketString(body.ConversationID, "conversation_id") + if err != nil { + _ = c.writeError(req.ID, err.Error()) + return + } + if body.BaseMessageRef == nil { + _ = c.writeError(req.ID, "base_message_ref is required") + return + } + if err := validateChatMessageRef(body.BaseMessageRef); err != nil { + _ = c.writeError(req.ID, err.Error()) + return + } + + response, err := c.awaitAgentResponse(req.ID, &gatewayv1.GatewayEnvelope{ + RequestId: req.ID, + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.GatewayEnvelope_HistoryPrefix{ + HistoryPrefix: &gatewayv1.HistoryPrefixRequest{ + ConversationId: conversationID, + MaxMessages: body.MaxMessages, + BaseMessageRef: buildProtoChatMessageRef(body.BaseMessageRef), + }, + }, + }) + if err != nil { + _ = c.writeError(req.ID, websocketErrorMessage(err)) + return + } + if errResp := response.GetError(); errResp != nil { + _ = c.writeError(req.ID, errResp.GetMessage()) + return + } + + resp := response.GetHistoryPrefixResp() + if resp == nil { + _ = c.writeError(req.ID, "unexpected agent response") + return + } + + _ = c.writeResponse(req.ID, map[string]any{ + "conversation_id": resp.GetConversationId(), + "messages_json": resp.GetMessagesJson(), + "total_message_count": resp.GetTotalMessageCount(), + "returned_message_count": resp.GetReturnedMessageCount(), + "has_more": resp.GetHasMore(), + "conversation": websocketConversationSummaryPayload(resp.GetConversation()), + }) +} + func (c *websocketConnection) handleHistoryRename(req websocketRequest) { type payload struct { ConversationID string `json:"conversation_id"` diff --git a/crates/agent-gateway/internal/server/websocket_routes.go b/crates/agent-gateway/internal/server/websocket_routes.go index e9550de2..84ae7e36 100644 --- a/crates/agent-gateway/internal/server/websocket_routes.go +++ b/crates/agent-gateway/internal/server/websocket_routes.go @@ -20,6 +20,7 @@ var websocketRequestHandlers = map[string]websocketRequestHandler{ "history.workdirs": (*websocketConnection).handleHistoryWorkdirs, "history.shared_list": (*websocketConnection).handleHistorySharedList, "history.get": (*websocketConnection).handleHistoryGet, + "history.prefix": (*websocketConnection).handleHistoryPrefix, "history.rename": (*websocketConnection).handleHistoryRename, "history.pin": (*websocketConnection).handleHistoryPin, "history.share.get": (*websocketConnection).handleHistoryShareGet, diff --git a/crates/agent-gateway/internal/server/websocket_routes_test.go b/crates/agent-gateway/internal/server/websocket_routes_test.go index c7b5253b..0d589279 100644 --- a/crates/agent-gateway/internal/server/websocket_routes_test.go +++ b/crates/agent-gateway/internal/server/websocket_routes_test.go @@ -24,6 +24,7 @@ func TestWebsocketRequestHandlersCoverKnownProtocolTypes(t *testing.T) { "history.workdirs", "history.shared_list", "history.get", + "history.prefix", "history.rename", "history.pin", "history.share.get", diff --git a/crates/agent-gateway/proto/v1/gateway.proto b/crates/agent-gateway/proto/v1/gateway.proto index 6c18d087..c63c1a1b 100644 --- a/crates/agent-gateway/proto/v1/gateway.proto +++ b/crates/agent-gateway/proto/v1/gateway.proto @@ -33,6 +33,7 @@ message GatewayEnvelope { HistoryGetRequest history_get = 31; HistoryRenameRequest history_rename = 32; HistoryDeleteRequest history_delete = 33; + HistoryPrefixRequest history_prefix = 34; HistoryPinRequest history_pin = 35; HistoryShareGetRequest history_share_get = 36; HistoryShareSetRequest history_share_set = 37; @@ -82,6 +83,7 @@ message AgentEnvelope { HistoryRenameResponse history_rename_resp = 32; HistoryDeleteResponse history_delete_resp = 33; HistorySyncEvent history_sync = 34; + HistoryPrefixResponse history_prefix_resp = 35; HistoryPinResponse history_pin_resp = 36; HistoryShareGetResponse history_share_get_resp = 37; HistoryShareSetResponse history_share_set_resp = 38; @@ -456,6 +458,10 @@ message ChatRequest { message ChatMessageRef { int32 segment_index = 1; int32 message_index = 2; + string segment_id = 3; + string message_id = 4; + string role = 5; + string content_hash = 6; } message CancelChatRequest { @@ -558,6 +564,21 @@ message HistoryGetResponse { ConversationSummary conversation = 6; } +message HistoryPrefixRequest { + string conversation_id = 1; + int32 max_messages = 2; + ChatMessageRef base_message_ref = 3; +} + +message HistoryPrefixResponse { + string conversation_id = 1; + string messages_json = 2; + int32 total_message_count = 3; + int32 returned_message_count = 4; + bool has_more = 5; + ConversationSummary conversation = 6; +} + message HistoryRenameRequest { string conversation_id = 1; string title = 2; diff --git a/crates/agent-gateway/web/src/app/GatewayApp.tsx b/crates/agent-gateway/web/src/app/GatewayApp.tsx index fc7e6476..2ce89b32 100644 --- a/crates/agent-gateway/web/src/app/GatewayApp.tsx +++ b/crates/agent-gateway/web/src/app/GatewayApp.tsx @@ -198,7 +198,6 @@ import { WorkspaceOverlayHost } from "./WorkspaceOverlayHost"; import { createConversationRuntimeEntry, createWorkspaceProjectFromPath, - findUserMessageRefByOrdinal, formatTranslation, getDefaultWorkspaceProjectPath, hasDetachedHistorySelection, @@ -209,7 +208,6 @@ import { resolveVisibleConversationId, shouldOpenSidebarByDefault, toChatHistorySummary, - truncateChatEntriesFromMessageRef, } from "./historyUtils"; import type { ConversationRuntimeEntry, @@ -416,6 +414,10 @@ export default function GatewayApp() { const titlePositionLockedConversationIdsRef = useRef>(new Set()); const titlePositionLockTimeoutsRef = useRef>(new Map()); const blockedHistoryHydrationConversationIdsRef = useRef>(new Set()); + const editResendGenerationRef = useRef(0); + const activeEditResendTransactionsRef = useRef< + Map + >(new Map()); const visibleHistorySnapshotRefreshSeqRef = useRef>(new Map()); const restoredPageHistoryRefreshAtRef = useRef>(new Map()); const historyLoadSequenceRef = useRef(0); @@ -1102,11 +1104,21 @@ export default function GatewayApp() { ); const refreshVisibleConversationHistorySnapshot = useCallback( - async (targetConversationId: string, currentApi = api, options?: { allowIdle?: boolean }) => { + async ( + targetConversationId: string, + currentApi = api, + options?: { allowIdle?: boolean; allowDuringEditTransaction?: boolean }, + ) => { const conversationIdValue = targetConversationId.trim(); if (!currentApi || !conversationIdValue) { return; } + if ( + options?.allowDuringEditTransaction !== true && + activeEditResendTransactionsRef.current.has(conversationIdValue) + ) { + return; + } const isStillVisible = () => resolveVisibleConversationId(selectedHistoryIdRef.current, conversationIdRef.current) === @@ -3094,6 +3106,9 @@ export default function GatewayApp() { if (!currentApi || !conversationIdValue) { return false; } + if (activeEditResendTransactionsRef.current.has(conversationIdValue)) { + return false; + } const isStillVisible = () => resolveVisibleConversationId(selectedHistoryIdRef.current, conversationIdRef.current) === @@ -3753,6 +3768,8 @@ export default function GatewayApp() { handleTunnelManagerChatEvent(event); if (terminalEvent) { terminalEventSeen = true; + clearRuntimeStartingStatusTimer(); + clearConversationStreamingState(activeConversationId); markCompletedLiveStream(activeConversationId); commitTerminalConversationLiveStream(activeConversationId); refreshHistoryAfterCompletedLiveStream(activeConversationId, api); @@ -3793,7 +3810,9 @@ export default function GatewayApp() { silent: true, }); } - blockedHistoryHydrationConversationIdsRef.current.delete(activeConversationId); + if (!options?.editMessageRef) { + blockedHistoryHydrationConversationIdsRef.current.delete(activeConversationId); + } if ( pendingDraftConversationMigrationRef.current?.draftConversationId === activeConversationId ) { @@ -4812,25 +4831,6 @@ export default function GatewayApp() { }); } - const resolveUserMessageRef = useCallback( - async (userOrdinal: number, text: string, uploadedFiles: PendingUploadedFile[]) => { - const activeConversationId = conversationIdRef.current.trim(); - if ( - !api || - !activeConversationId || - isLocalDraftConversationId(activeConversationId) || - hasDetachedHistorySelection(selectedHistoryIdRef.current, activeConversationId) - ) { - return null; - } - - const detail = await api.getHistory(activeConversationId); - const entries = await parseHistoryMessagesJsonAsync(detail.messages_json); - return findUserMessageRefByOrdinal(entries, userOrdinal, text, uploadedFiles); - }, - [api], - ); - const handleResendFromEdit = useCallback( async (messageRef: HistoryMessageRef, text: string, uploadedFiles: PendingUploadedFile[]) => { const activeConversationId = conversationIdRef.current.trim(); @@ -4846,6 +4846,21 @@ export default function GatewayApp() { if (!normalized && uploadedFiles.length === 0) { return; } + const randomPart = + typeof globalThis.crypto?.randomUUID === "function" + ? globalThis.crypto.randomUUID() + : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; + const clientRequestId = `webui-chat.edit_resend-${activeConversationId}-${randomPart}`; + const generation = editResendGenerationRef.current + 1; + editResendGenerationRef.current = generation; + activeEditResendTransactionsRef.current.set(activeConversationId, { + generation, + clientRequestId, + }); + const isCurrentEditTransaction = () => { + const current = activeEditResendTransactionsRef.current.get(activeConversationId); + return current?.generation === generation && current.clientRequestId === clientRequestId; + }; setHistoryError(null); setChatError(null); @@ -4856,59 +4871,15 @@ export default function GatewayApp() { markVisibleConversationRevision(); try { - const currentRuntime = - conversationIdRef.current.trim() === activeConversationId && - (selectedHistoryIdRef.current.trim() === "" || - selectedHistoryIdRef.current.trim() === activeConversationId) - ? buildVisibleRuntimeEntry() - : (conversationRuntimeCacheRef.current.get(activeConversationId) ?? - buildVisibleRuntimeEntry()); - const locallyTruncatedEntries = truncateChatEntriesFromMessageRef( - currentRuntime.messages, - messageRef, - ); - const canUseLocalTruncate = locallyTruncatedEntries !== currentRuntime.messages; - let detail: HistoryDetail; - let entries: ChatEntry[]; - if (canUseLocalTruncate) { - entries = locallyTruncatedEntries; - detail = { - conversation_id: activeConversationId, - messages_json: "", - total_message_count: entries.length, - returned_message_count: entries.length, - has_more: false, - conversation: - selectedHistoryRef.current?.conversation_id === activeConversationId - ? selectedHistoryRef.current.conversation - : (pickConversationSummary(historyItemsRef.current, activeConversationId) ?? - undefined), - }; - } else { - const hydratedDetail = await api.getHistory(activeConversationId); - const hydratedEntries = await parseHistoryMessagesJsonAsync( - hydratedDetail.messages_json, - ); - const hydratedTruncatedEntries = truncateChatEntriesFromMessageRef( - hydratedEntries, - messageRef, - ); - entries = - hydratedTruncatedEntries === hydratedEntries - ? currentRuntime.messages - : hydratedTruncatedEntries; - detail = { - conversation_id: activeConversationId, - messages_json: "", - total_message_count: entries.length, - returned_message_count: entries.length, - has_more: false, - conversation: - hydratedDetail.conversation ?? - selectedHistoryRef.current?.conversation ?? - pickConversationSummary(historyItemsRef.current, activeConversationId) ?? - undefined, - }; + const detail = await api.getHistoryPrefix(activeConversationId, messageRef, { + maxMessages: HISTORY_DETAIL_INITIAL_MAX_MESSAGES, + }); + if (!isCurrentEditTransaction()) { + return; + } + const entries = await parseHistoryMessagesJsonAsync(detail.messages_json); + if (!isCurrentEditTransaction()) { + return; } const nextRuntime = createConversationRuntimeEntry({ messages: entries, @@ -4936,21 +4907,30 @@ export default function GatewayApp() { conversationId: activeConversationId, uploadedFiles, editMessageRef: messageRef, + clientRequestId, }) ?? Promise.resolve(); - blockedHistoryHydrationConversationIdsRef.current.delete(activeConversationId); await resendPromise; + if (isCurrentEditTransaction()) { + await refreshVisibleConversationHistorySnapshot(activeConversationId, api, { + allowIdle: true, + allowDuringEditTransaction: true, + }); + } } catch (error) { setChatError(asErrorMessage(error, "回溯历史消息失败")); } finally { - blockedHistoryHydrationConversationIdsRef.current.delete(activeConversationId); + if (isCurrentEditTransaction()) { + activeEditResendTransactionsRef.current.delete(activeConversationId); + blockedHistoryHydrationConversationIdsRef.current.delete(activeConversationId); + } } }, [ api, - buildVisibleRuntimeEntry, clearConversationLiveStream, invalidateHistoryLoad, markVisibleConversationRevision, + refreshVisibleConversationHistorySnapshot, syncVisibleConversationRuntime, updateHistoryItems, ], @@ -6039,9 +6019,6 @@ export default function GatewayApp() { gitClient={gitClient} onLoadUploadedImagePreview={handleLoadUploadedImagePreview} onResendFromEdit={hasDetachedSelection ? undefined : handleResendFromEdit} - onResolveUserMessageRef={ - hasDetachedSelection ? undefined : resolveUserMessageRef - } /> {historySwitchOverlay ? ( diff --git a/crates/agent-gateway/web/src/app/historyUtils.ts b/crates/agent-gateway/web/src/app/historyUtils.ts index 3067f714..a6c08a42 100644 --- a/crates/agent-gateway/web/src/app/historyUtils.ts +++ b/crates/agent-gateway/web/src/app/historyUtils.ts @@ -1,6 +1,4 @@ import type { ChatHistorySummary } from "@/lib/chat/chatHistory"; -import type { HistoryMessageRef } from "@/lib/chat/conversationState"; -import type { PendingUploadedFile } from "@/lib/chat/uploadedFiles"; import type { ConversationSummary } from "@/lib/gatewayTypes"; import { buildGatewaySettingsSyncPayload } from "@/lib/settings/sync"; import { @@ -10,7 +8,7 @@ import { type SelectedModel, type WorkspaceProject, } from "@/lib/settings"; -import { formatConversationTitle, type ChatEntry } from "@/lib/chatUi"; +import { formatConversationTitle } from "@/lib/chatUi"; import { isLocalDraftConversationId } from "@/lib/localDraftConversation"; import { fallbackWorkspaceProjectName, @@ -145,23 +143,6 @@ export function createConversationRuntimeEntry( }; } -function historyMessageRefsEqual(a: HistoryMessageRef | undefined, b: HistoryMessageRef) { - return a?.segmentIndex === b.segmentIndex && a?.messageIndex === b.messageIndex; -} - -export function truncateChatEntriesFromMessageRef( - entries: ChatEntry[], - messageRef: HistoryMessageRef, -) { - const targetIndex = entries.findIndex( - (entry) => entry.kind === "user" && historyMessageRefsEqual(entry.messageRef, messageRef), - ); - if (targetIndex < 0) { - return entries; - } - return entries.slice(0, targetIndex); -} - export function resolveVisibleConversationId( selectedHistoryId: string, conversationId: string, @@ -192,52 +173,3 @@ export function hasDetachedHistorySelection( const activeConversationId = conversationId.trim(); return selectedId !== "" && selectedId !== activeConversationId; } - -function uploadedFilesEqual(left: PendingUploadedFile[], right: PendingUploadedFile[]) { - if (left.length !== right.length) { - return false; - } - return left.every((file, index) => { - const other = right[index]; - return ( - other !== undefined && - file.relativePath === other.relativePath && - file.fileName === other.fileName && - file.kind === other.kind && - file.sizeBytes === other.sizeBytes - ); - }); -} - -export function findUserMessageRefByOrdinal( - entries: ChatEntry[], - userOrdinal: number, - text: string, - uploadedFiles: PendingUploadedFile[], -) { - if (userOrdinal < 0) { - return null; - } - const userEntries = entries.filter((entry) => entry.kind === "user"); - const ordinalEntry = userEntries[userOrdinal]; - if ( - ordinalEntry?.messageRef && - ordinalEntry.text === text && - uploadedFilesEqual(ordinalEntry.attachments, uploadedFiles) - ) { - return ordinalEntry.messageRef; - } - - for (let index = userEntries.length - 1; index >= 0; index -= 1) { - const entry = userEntries[index]; - if ( - entry?.messageRef && - entry.text === text && - uploadedFilesEqual(entry.attachments, uploadedFiles) - ) { - return entry.messageRef; - } - } - - return null; -} diff --git a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx index 45bd90b3..96d6350f 100644 --- a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx +++ b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx @@ -85,11 +85,6 @@ type GatewayTranscriptProps = { text: string, uploadedFiles: PendingUploadedFile[], ) => void; - onResolveUserMessageRef?: ( - userOrdinal: number, - text: string, - uploadedFiles: PendingUploadedFile[], - ) => Promise; readOnly?: boolean; redactToolContent?: boolean; }; @@ -744,11 +739,6 @@ const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { text: string, uploadedFiles: PendingUploadedFile[], ) => void; - onResolveUserMessageRef?: ( - userOrdinal: number, - text: string, - uploadedFiles: PendingUploadedFile[], - ) => Promise; readOnly?: boolean; redactToolContent?: boolean; }) { @@ -766,15 +756,12 @@ const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { gitClient, onLoadUploadedImagePreview, onResendFromEdit, - onResolveUserMessageRef, readOnly = false, redactToolContent = false, } = props; const { locale, t } = useLocale(); const [copiedMessageId, setCopiedMessageId] = useState(null); const [editingMessageId, setEditingMessageId] = useState(null); - const [resolvingEditMessageId, setResolvingEditMessageId] = useState(null); - const [resolvedMessageRefs, setResolvedMessageRefs] = useState>({}); const commitDetailsCacheRef = useRef(new Map()); const historyIdentityKey = `${conversationId ?? ""}\n${items[0]?.id ?? ""}`; @@ -816,8 +803,6 @@ const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { useEffect(() => { setEditingMessageId(null); - setResolvingEditMessageId(null); - setResolvedMessageRefs({}); commitDetailsCacheRef.current.clear(); }, [historyIdentityKey]); @@ -896,13 +881,16 @@ const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { const item = virtualItem.item; if (item.kind === "user") { - const userOrdinal = item.userOrdinal; const isCopied = copiedMessageId === item.id; const isEditing = editingMessageId === item.id; - const resolvedMessageRef = resolvedMessageRefs[item.id]; - const effectiveMessageRef = item.messageRef ?? resolvedMessageRef; - const isResolvingEdit = resolvingEditMessageId === item.id; - const editDisabled = readOnly || isStreaming || isResolvingEdit || !onResendFromEdit; + const effectiveMessageRef = item.messageRef; + const missingStableRef = !effectiveMessageRef; + const editDisabled = readOnly || isStreaming || !onResendFromEdit || missingStableRef; + const editTitle = missingStableRef + ? locale === "en-US" + ? "This older message cannot be edited because it has no stable message identifier." + : "旧历史缺少稳定消息标识,无法编辑重发" + : t("chat.edit"); const { visibleFiles, pastedTextFiles } = splitUserAttachmentsForDisplay( item.attachments, item.text, @@ -970,38 +958,13 @@ const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { From 7ce317ebbffd2afc2861e29b908faa39e4bae2d9 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Tue, 23 Jun 2026 15:52:09 +0800 Subject: [PATCH 4/8] feat(ssh): add custom tunnel host picker --- .../web/src/components/icons.tsx | 2 + .../project-tools/SshTunnelPanel.tsx | 117 ++++++++++++++---- crates/agent-gui/src/components/icons.tsx | 2 + .../project-tools/SshTunnelPanel.tsx | 117 ++++++++++++++---- 4 files changed, 188 insertions(+), 50 deletions(-) diff --git a/crates/agent-gateway/web/src/components/icons.tsx b/crates/agent-gateway/web/src/components/icons.tsx index 129b6724..ff4f6f4c 100644 --- a/crates/agent-gateway/web/src/components/icons.tsx +++ b/crates/agent-gateway/web/src/components/icons.tsx @@ -1,6 +1,7 @@ import { useId, type ComponentType, type SVGProps } from "react"; import McpLogoSource from "~icons/gravity-ui/logo-mcp"; +import ConnectionIconSource from "~icons/gravity-ui/plug-connection"; import AlertCircleSource from "~icons/lucide/circle-alert"; import AlertTriangleSource from "~icons/lucide/triangle-alert"; import ClaudeSource from "~icons/logos/claude-icon"; @@ -447,6 +448,7 @@ export const Circle = createIcon(CircleSource); export const ClipboardPaste = createIcon(ClipboardPasteSource); export const Clock3 = createIcon(Clock3Source); export const Cloud = createIcon(CloudSource); +export const ConnectionIcon = createIcon(ConnectionIconSource); export const Copy = createIcon(CopySource); export const Cpu = createIcon(CpuSource); export const Download = createIcon(DownloadSource); diff --git a/crates/agent-gateway/web/src/components/project-tools/SshTunnelPanel.tsx b/crates/agent-gateway/web/src/components/project-tools/SshTunnelPanel.tsx index 9086161b..93d922ed 100644 --- a/crates/agent-gateway/web/src/components/project-tools/SshTunnelPanel.tsx +++ b/crates/agent-gateway/web/src/components/project-tools/SshTunnelPanel.tsx @@ -15,11 +15,11 @@ import { Check, ChevronDown, Clock3, + ConnectionIcon, FolderTree, Globe, Key, Loader2, - Plus, RefreshCw, Server, Settings, @@ -31,6 +31,12 @@ import { } from "../icons"; import { Button } from "../ui/button"; import { useConfirmDialog } from "../ui/confirm-dialog"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; type SshTunnelScope = "project" | "all"; type SshTunnelView = "list" | "settings" | "create"; @@ -218,6 +224,7 @@ export function SshTunnelPanel(props: SshTunnelPanelProps) { const [scope, setScope] = useState("project"); const [view, setView] = useState("list"); const [createHostId, setCreateHostId] = useState(""); + const [createHostMenuOpen, setCreateHostMenuOpen] = useState(false); const [createTitle, setCreateTitle] = useState(""); const [createSftpEnabled, setCreateSftpEnabled] = useState(false); const [creating, setCreating] = useState(false); @@ -709,34 +716,94 @@ export function SshTunnelPanel(props: SshTunnelPanelProps) { }} >
-
) : null} -
+
{/* macOS material rim-light */}
- {isSending ? ( - <> - - - - ) : null}
From f457b8a4bc24e3ff270c6f0d03a30939c4997585 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Wed, 24 Jun 2026 16:32:00 +0800 Subject: [PATCH 6/8] fix(gateway): sync queued chat runs across gui and webui --- .../agent-gateway/internal/handler/types.go | 1 + .../internal/proto/v1/gateway.pb.go | 1231 +++++++++++------ .../internal/server/chat_commands.go | 11 + .../internal/server/chat_payloads.go | 2 + .../internal/server/http_chat.go | 11 +- .../internal/server/http_test.go | 120 +- .../internal/server/websocket.go | 59 +- .../server/websocket_chat_queue_handlers.go | 74 + .../internal/server/websocket_payloads.go | 8 + .../internal/server/websocket_routes.go | 8 + .../internal/server/websocket_routes_test.go | 8 + .../agent-gateway/internal/session/manager.go | 17 +- .../internal/session/manager_chat_queue.go | 47 + .../internal/session/manager_chat_runs.go | 138 +- .../internal/session/manager_state.go | 5 + .../session/sqlite_chat_event_store.go | 2 + crates/agent-gateway/proto/v1/gateway.proto | 31 + .../test/session/manager_test.go | 119 ++ .../test/webui/chat-turn-queue.test.mjs | 9 +- .../test/webui/gateway-socket-client.test.mjs | 194 ++- .../test/webui/history-chat-ui.test.mjs | 291 +++- .../agent-gateway/web/src/app/GatewayApp.tsx | 846 +++++++---- crates/agent-gateway/web/src/app/chatDraft.ts | 4 + .../web/src/app/chatEventUtils.ts | 4 +- crates/agent-gateway/web/src/app/types.ts | 4 +- .../web/src/components/GatewayTranscript.tsx | 179 ++- .../src/components/chat/MentionComposer.tsx | 49 +- crates/agent-gateway/web/src/i18n/config.ts | 4 + .../web/src/lib/chat/mentionReferences.ts | 80 ++ crates/agent-gateway/web/src/lib/chatUi.ts | 70 + .../web/src/lib/gatewaySocket.ts | 336 ++++- .../web/src/lib/gatewaySocket.worker.ts | 48 + .../agent-gateway/web/src/lib/gatewayTypes.ts | 33 +- .../agent-gateway/web/src/lib/historySync.ts | 15 +- .../web/src/lib/liveConversationCommit.ts | 25 +- .../src/lib/liveConversationStreamStore.ts | 29 +- .../web/src/pages/chat/ChatComposerBar.tsx | 370 +++-- .../web/src/pages/chat/queue/chatTurnQueue.ts | 3 + .../src/commands/integration/gateway.rs | 57 +- .../src-tauri/src/commands/workspace/fs.rs | 4 +- crates/agent-gui/src-tauri/src/lib.rs | 5 + .../src-tauri/src/runtime/managed_process.rs | 4 +- .../src-tauri/src/runtime/shell_runner.rs | 12 +- .../src-tauri/src/runtime/terminal.rs | 10 +- .../src-tauri/src/services/gateway.rs | 419 +++++- .../src-tauri/src/services/gateway_bridge.rs | 14 +- .../conversation/run/gatewayBridgeEvents.ts | 26 +- crates/agent-gui/src/pages/ChatPage.tsx | 584 +++++++- .../pages/chat/gateway/gatewayBridgeTypes.ts | 1 + .../chat/gateway/useGatewayBridgeBatcher.ts | 3 +- .../chat/gateway/useGatewayBridgeListeners.ts | 324 +++-- .../src/pages/chat/queue/chatTurnQueue.ts | 37 + .../test/chat/conversation-state.test.mjs | 17 +- 53 files changed, 4791 insertions(+), 1211 deletions(-) create mode 100644 crates/agent-gateway/internal/server/websocket_chat_queue_handlers.go create mode 100644 crates/agent-gateway/internal/session/manager_chat_queue.go create mode 100644 crates/agent-gateway/web/src/lib/chat/mentionReferences.ts diff --git a/crates/agent-gateway/internal/handler/types.go b/crates/agent-gateway/internal/handler/types.go index b88ca763..30cf8097 100644 --- a/crates/agent-gateway/internal/handler/types.go +++ b/crates/agent-gateway/internal/handler/types.go @@ -37,6 +37,7 @@ type ChatRequestBody struct { Workdir string `json:"workdir,omitempty"` SelectedSystemTools []string `json:"selected_system_tools,omitempty"` UploadedFiles []ChatUploadedFileBody `json:"uploaded_files,omitempty"` + QueuePolicy string `json:"queue_policy,omitempty"` } type CancelChatRequestBody struct { diff --git a/crates/agent-gateway/internal/proto/v1/gateway.pb.go b/crates/agent-gateway/internal/proto/v1/gateway.pb.go index 0a38a3c5..0dd2b470 100644 --- a/crates/agent-gateway/internal/proto/v1/gateway.pb.go +++ b/crates/agent-gateway/internal/proto/v1/gateway.pb.go @@ -108,6 +108,7 @@ const ( ChatEvent_ERROR ChatEvent_ChatEventType = 5 ChatEvent_TOOL_STATUS ChatEvent_ChatEventType = 6 ChatEvent_HOSTED_SEARCH ChatEvent_ChatEventType = 7 + ChatEvent_USER_MESSAGE ChatEvent_ChatEventType = 8 ) // Enum value maps for ChatEvent_ChatEventType. @@ -121,6 +122,7 @@ var ( 5: "ERROR", 6: "TOOL_STATUS", 7: "HOSTED_SEARCH", + 8: "USER_MESSAGE", } ChatEvent_ChatEventType_value = map[string]int32{ "TOKEN": 0, @@ -131,6 +133,7 @@ var ( "ERROR": 5, "TOOL_STATUS": 6, "HOSTED_SEARCH": 7, + "USER_MESSAGE": 8, } ) @@ -158,7 +161,7 @@ func (x ChatEvent_ChatEventType) Number() protoreflect.EnumNumber { // Deprecated: Use ChatEvent_ChatEventType.Descriptor instead. func (ChatEvent_ChatEventType) EnumDescriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{40, 0} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{43, 0} } type AuthRequest struct { @@ -328,6 +331,7 @@ type GatewayEnvelope struct { // *GatewayEnvelope_TunnelControlResp // *GatewayEnvelope_TunnelFrame // *GatewayEnvelope_SettingsResetSshKnownHost + // *GatewayEnvelope_ChatQueue Payload isGatewayEnvelope_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -753,6 +757,15 @@ func (x *GatewayEnvelope) GetSettingsResetSshKnownHost() *SettingsResetSshKnownH return nil } +func (x *GatewayEnvelope) GetChatQueue() *ChatQueueRequest { + if x != nil { + if x, ok := x.Payload.(*GatewayEnvelope_ChatQueue); ok { + return x.ChatQueue + } + } + return nil +} + type isGatewayEnvelope_Payload interface { isGatewayEnvelope_Payload() } @@ -921,6 +934,10 @@ type GatewayEnvelope_SettingsResetSshKnownHost struct { SettingsResetSshKnownHost *SettingsResetSshKnownHostRequest `protobuf:"bytes,72,opt,name=settings_reset_ssh_known_host,json=settingsResetSshKnownHost,proto3,oneof"` } +type GatewayEnvelope_ChatQueue struct { + ChatQueue *ChatQueueRequest `protobuf:"bytes,73,opt,name=chat_queue,json=chatQueue,proto3,oneof"` +} + func (*GatewayEnvelope_ChatCommand) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_CronManage) isGatewayEnvelope_Payload() {} @@ -1003,6 +1020,8 @@ func (*GatewayEnvelope_TunnelFrame) isGatewayEnvelope_Payload() {} func (*GatewayEnvelope_SettingsResetSshKnownHost) isGatewayEnvelope_Payload() {} +func (*GatewayEnvelope_ChatQueue) isGatewayEnvelope_Payload() {} + type AgentEnvelope struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` @@ -1050,6 +1069,8 @@ type AgentEnvelope struct { // *AgentEnvelope_FsReadWorkspaceImageResp // *AgentEnvelope_SftpResponse // *AgentEnvelope_SftpEvent + // *AgentEnvelope_ChatQueueResp + // *AgentEnvelope_ChatQueueEvent // *AgentEnvelope_TunnelControl // *AgentEnvelope_TunnelControlResp // *AgentEnvelope_TunnelFrame @@ -1482,6 +1503,24 @@ func (x *AgentEnvelope) GetSftpEvent() *SftpEvent { return nil } +func (x *AgentEnvelope) GetChatQueueResp() *ChatQueueResponse { + if x != nil { + if x, ok := x.Payload.(*AgentEnvelope_ChatQueueResp); ok { + return x.ChatQueueResp + } + } + return nil +} + +func (x *AgentEnvelope) GetChatQueueEvent() *ChatQueueEvent { + if x != nil { + if x, ok := x.Payload.(*AgentEnvelope_ChatQueueEvent); ok { + return x.ChatQueueEvent + } + } + return nil +} + func (x *AgentEnvelope) GetTunnelControl() *TunnelControlRequest { if x != nil { if x, ok := x.Payload.(*AgentEnvelope_TunnelControl); ok { @@ -1713,6 +1752,14 @@ type AgentEnvelope_SftpEvent struct { SftpEvent *SftpEvent `protobuf:"bytes,74,opt,name=sftp_event,json=sftpEvent,proto3,oneof"` } +type AgentEnvelope_ChatQueueResp struct { + ChatQueueResp *ChatQueueResponse `protobuf:"bytes,75,opt,name=chat_queue_resp,json=chatQueueResp,proto3,oneof"` +} + +type AgentEnvelope_ChatQueueEvent struct { + ChatQueueEvent *ChatQueueEvent `protobuf:"bytes,76,opt,name=chat_queue_event,json=chatQueueEvent,proto3,oneof"` +} + type AgentEnvelope_TunnelControl struct { TunnelControl *TunnelControlRequest `protobuf:"bytes,67,opt,name=tunnel_control,json=tunnelControl,proto3,oneof"` } @@ -1823,6 +1870,10 @@ func (*AgentEnvelope_SftpResponse) isAgentEnvelope_Payload() {} func (*AgentEnvelope_SftpEvent) isAgentEnvelope_Payload() {} +func (*AgentEnvelope_ChatQueueResp) isAgentEnvelope_Payload() {} + +func (*AgentEnvelope_ChatQueueEvent) isAgentEnvelope_Payload() {} + func (*AgentEnvelope_TunnelControl) isAgentEnvelope_Payload() {} func (*AgentEnvelope_TunnelControlResp) isAgentEnvelope_Payload() {} @@ -4632,6 +4683,7 @@ type ChatRequest struct { UploadedFiles []*ChatUploadedFile `protobuf:"bytes,7,rep,name=uploaded_files,json=uploadedFiles,proto3" json:"uploaded_files,omitempty"` ClientRequestId string `protobuf:"bytes,8,opt,name=client_request_id,json=clientRequestId,proto3" json:"client_request_id,omitempty"` RuntimeControls *ChatRuntimeControls `protobuf:"bytes,9,opt,name=runtime_controls,json=runtimeControls,proto3" json:"runtime_controls,omitempty"` + QueuePolicy string `protobuf:"bytes,10,opt,name=queue_policy,json=queuePolicy,proto3" json:"queue_policy,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4729,6 +4781,13 @@ func (x *ChatRequest) GetRuntimeControls() *ChatRuntimeControls { return nil } +func (x *ChatRequest) GetQueuePolicy() string { + if x != nil { + return x.QueuePolicy + } + return "" +} + type ChatMessageRef struct { state protoimpl.MessageState `protogen:"open.v1"` SegmentIndex int32 `protobuf:"varint,1,opt,name=segment_index,json=segmentIndex,proto3" json:"segment_index,omitempty"` @@ -4925,6 +4984,250 @@ func (x *ChatCommandRequest) GetCancel() *CancelChatRequest { return nil } +type ChatQueueRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` + ConversationId string `protobuf:"bytes,2,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` + ItemId string `protobuf:"bytes,3,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` + Direction string `protobuf:"bytes,4,opt,name=direction,proto3" json:"direction,omitempty"` + Revision uint64 `protobuf:"varint,5,opt,name=revision,proto3" json:"revision,omitempty"` + DraftJson string `protobuf:"bytes,6,opt,name=draft_json,json=draftJson,proto3" json:"draft_json,omitempty"` + UploadedFilesJson string `protobuf:"bytes,7,opt,name=uploaded_files_json,json=uploadedFilesJson,proto3" json:"uploaded_files_json,omitempty"` + RequestJson string `protobuf:"bytes,8,opt,name=request_json,json=requestJson,proto3" json:"request_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChatQueueRequest) Reset() { + *x = ChatQueueRequest{} + mi := &file_proto_v1_gateway_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChatQueueRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChatQueueRequest) ProtoMessage() {} + +func (x *ChatQueueRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChatQueueRequest.ProtoReflect.Descriptor instead. +func (*ChatQueueRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{40} +} + +func (x *ChatQueueRequest) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *ChatQueueRequest) GetConversationId() string { + if x != nil { + return x.ConversationId + } + return "" +} + +func (x *ChatQueueRequest) GetItemId() string { + if x != nil { + return x.ItemId + } + return "" +} + +func (x *ChatQueueRequest) GetDirection() string { + if x != nil { + return x.Direction + } + return "" +} + +func (x *ChatQueueRequest) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *ChatQueueRequest) GetDraftJson() string { + if x != nil { + return x.DraftJson + } + return "" +} + +func (x *ChatQueueRequest) GetUploadedFilesJson() string { + if x != nil { + return x.UploadedFilesJson + } + return "" +} + +func (x *ChatQueueRequest) GetRequestJson() string { + if x != nil { + return x.RequestJson + } + return "" +} + +type ChatQueueResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + SnapshotJson string `protobuf:"bytes,3,opt,name=snapshot_json,json=snapshotJson,proto3" json:"snapshot_json,omitempty"` + ItemJson string `protobuf:"bytes,4,opt,name=item_json,json=itemJson,proto3" json:"item_json,omitempty"` + ErrorCode string `protobuf:"bytes,5,opt,name=error_code,json=errorCode,proto3" json:"error_code,omitempty"` + Revision uint64 `protobuf:"varint,6,opt,name=revision,proto3" json:"revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChatQueueResponse) Reset() { + *x = ChatQueueResponse{} + mi := &file_proto_v1_gateway_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChatQueueResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChatQueueResponse) ProtoMessage() {} + +func (x *ChatQueueResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChatQueueResponse.ProtoReflect.Descriptor instead. +func (*ChatQueueResponse) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{41} +} + +func (x *ChatQueueResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *ChatQueueResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ChatQueueResponse) GetSnapshotJson() string { + if x != nil { + return x.SnapshotJson + } + return "" +} + +func (x *ChatQueueResponse) GetItemJson() string { + if x != nil { + return x.ItemJson + } + return "" +} + +func (x *ChatQueueResponse) GetErrorCode() string { + if x != nil { + return x.ErrorCode + } + return "" +} + +func (x *ChatQueueResponse) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +type ChatQueueEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` + SnapshotJson string `protobuf:"bytes,2,opt,name=snapshot_json,json=snapshotJson,proto3" json:"snapshot_json,omitempty"` + Revision uint64 `protobuf:"varint,3,opt,name=revision,proto3" json:"revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChatQueueEvent) Reset() { + *x = ChatQueueEvent{} + mi := &file_proto_v1_gateway_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChatQueueEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChatQueueEvent) ProtoMessage() {} + +func (x *ChatQueueEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_gateway_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChatQueueEvent.ProtoReflect.Descriptor instead. +func (*ChatQueueEvent) Descriptor() ([]byte, []int) { + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{42} +} + +func (x *ChatQueueEvent) GetConversationId() string { + if x != nil { + return x.ConversationId + } + return "" +} + +func (x *ChatQueueEvent) GetSnapshotJson() string { + if x != nil { + return x.SnapshotJson + } + return "" +} + +func (x *ChatQueueEvent) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + type ChatEvent struct { state protoimpl.MessageState `protogen:"open.v1"` Type ChatEvent_ChatEventType `protobuf:"varint,1,opt,name=type,proto3,enum=liveagent.gateway.v1.ChatEvent_ChatEventType" json:"type,omitempty"` @@ -4936,7 +5239,7 @@ type ChatEvent struct { func (x *ChatEvent) Reset() { *x = ChatEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[40] + mi := &file_proto_v1_gateway_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4948,7 +5251,7 @@ func (x *ChatEvent) String() string { func (*ChatEvent) ProtoMessage() {} func (x *ChatEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[40] + mi := &file_proto_v1_gateway_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4961,7 +5264,7 @@ func (x *ChatEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ChatEvent.ProtoReflect.Descriptor instead. func (*ChatEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{40} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{43} } func (x *ChatEvent) GetType() ChatEvent_ChatEventType { @@ -5002,7 +5305,7 @@ type ChatControlEvent struct { func (x *ChatControlEvent) Reset() { *x = ChatControlEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[41] + mi := &file_proto_v1_gateway_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5014,7 +5317,7 @@ func (x *ChatControlEvent) String() string { func (*ChatControlEvent) ProtoMessage() {} func (x *ChatControlEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[41] + mi := &file_proto_v1_gateway_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5027,7 +5330,7 @@ func (x *ChatControlEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ChatControlEvent.ProtoReflect.Descriptor instead. func (*ChatControlEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{41} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{44} } func (x *ChatControlEvent) GetRequestId() string { @@ -5106,7 +5409,7 @@ type RuntimeStatusEvent struct { func (x *RuntimeStatusEvent) Reset() { *x = RuntimeStatusEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[42] + mi := &file_proto_v1_gateway_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5118,7 +5421,7 @@ func (x *RuntimeStatusEvent) String() string { func (*RuntimeStatusEvent) ProtoMessage() {} func (x *RuntimeStatusEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[42] + mi := &file_proto_v1_gateway_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5131,7 +5434,7 @@ func (x *RuntimeStatusEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RuntimeStatusEvent.ProtoReflect.Descriptor instead. func (*RuntimeStatusEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{42} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{45} } func (x *RuntimeStatusEvent) GetWorkerId() string { @@ -5180,7 +5483,7 @@ type CronManageRequest struct { func (x *CronManageRequest) Reset() { *x = CronManageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[43] + mi := &file_proto_v1_gateway_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5192,7 +5495,7 @@ func (x *CronManageRequest) String() string { func (*CronManageRequest) ProtoMessage() {} func (x *CronManageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[43] + mi := &file_proto_v1_gateway_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5205,7 +5508,7 @@ func (x *CronManageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronManageRequest.ProtoReflect.Descriptor instead. func (*CronManageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{43} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{46} } func (x *CronManageRequest) GetAction() string { @@ -5239,7 +5542,7 @@ type CronManageResponse struct { func (x *CronManageResponse) Reset() { *x = CronManageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[44] + mi := &file_proto_v1_gateway_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5251,7 +5554,7 @@ func (x *CronManageResponse) String() string { func (*CronManageResponse) ProtoMessage() {} func (x *CronManageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[44] + mi := &file_proto_v1_gateway_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5264,7 +5567,7 @@ func (x *CronManageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronManageResponse.ProtoReflect.Descriptor instead. func (*CronManageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{44} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{47} } func (x *CronManageResponse) GetAction() string { @@ -5293,7 +5596,7 @@ type HistoryListRequest struct { func (x *HistoryListRequest) Reset() { *x = HistoryListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[45] + mi := &file_proto_v1_gateway_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5305,7 +5608,7 @@ func (x *HistoryListRequest) String() string { func (*HistoryListRequest) ProtoMessage() {} func (x *HistoryListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[45] + mi := &file_proto_v1_gateway_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5318,7 +5621,7 @@ func (x *HistoryListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryListRequest.ProtoReflect.Descriptor instead. func (*HistoryListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{45} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{48} } func (x *HistoryListRequest) GetPage() int32 { @@ -5359,7 +5662,7 @@ type HistoryListResponse struct { func (x *HistoryListResponse) Reset() { *x = HistoryListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[46] + mi := &file_proto_v1_gateway_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5371,7 +5674,7 @@ func (x *HistoryListResponse) String() string { func (*HistoryListResponse) ProtoMessage() {} func (x *HistoryListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[46] + mi := &file_proto_v1_gateway_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5384,7 +5687,7 @@ func (x *HistoryListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryListResponse.ProtoReflect.Descriptor instead. func (*HistoryListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{46} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{49} } func (x *HistoryListResponse) GetConversations() []*ConversationSummary { @@ -5421,7 +5724,7 @@ type ConversationSummary struct { func (x *ConversationSummary) Reset() { *x = ConversationSummary{} - mi := &file_proto_v1_gateway_proto_msgTypes[47] + mi := &file_proto_v1_gateway_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5433,7 +5736,7 @@ func (x *ConversationSummary) String() string { func (*ConversationSummary) ProtoMessage() {} func (x *ConversationSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[47] + mi := &file_proto_v1_gateway_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5446,7 +5749,7 @@ func (x *ConversationSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use ConversationSummary.ProtoReflect.Descriptor instead. func (*ConversationSummary) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{47} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{50} } func (x *ConversationSummary) GetId() string { @@ -5543,7 +5846,7 @@ type HistoryGetRequest struct { func (x *HistoryGetRequest) Reset() { *x = HistoryGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[48] + mi := &file_proto_v1_gateway_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5555,7 +5858,7 @@ func (x *HistoryGetRequest) String() string { func (*HistoryGetRequest) ProtoMessage() {} func (x *HistoryGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[48] + mi := &file_proto_v1_gateway_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5568,7 +5871,7 @@ func (x *HistoryGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryGetRequest.ProtoReflect.Descriptor instead. func (*HistoryGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{48} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{51} } func (x *HistoryGetRequest) GetConversationId() string { @@ -5599,7 +5902,7 @@ type HistoryGetResponse struct { func (x *HistoryGetResponse) Reset() { *x = HistoryGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[49] + mi := &file_proto_v1_gateway_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5611,7 +5914,7 @@ func (x *HistoryGetResponse) String() string { func (*HistoryGetResponse) ProtoMessage() {} func (x *HistoryGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[49] + mi := &file_proto_v1_gateway_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5624,7 +5927,7 @@ func (x *HistoryGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryGetResponse.ProtoReflect.Descriptor instead. func (*HistoryGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{49} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{52} } func (x *HistoryGetResponse) GetConversationId() string { @@ -5680,7 +5983,7 @@ type HistoryPrefixRequest struct { func (x *HistoryPrefixRequest) Reset() { *x = HistoryPrefixRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[50] + mi := &file_proto_v1_gateway_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5692,7 +5995,7 @@ func (x *HistoryPrefixRequest) String() string { func (*HistoryPrefixRequest) ProtoMessage() {} func (x *HistoryPrefixRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[50] + mi := &file_proto_v1_gateway_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5705,7 +6008,7 @@ func (x *HistoryPrefixRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPrefixRequest.ProtoReflect.Descriptor instead. func (*HistoryPrefixRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{50} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{53} } func (x *HistoryPrefixRequest) GetConversationId() string { @@ -5743,7 +6046,7 @@ type HistoryPrefixResponse struct { func (x *HistoryPrefixResponse) Reset() { *x = HistoryPrefixResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[51] + mi := &file_proto_v1_gateway_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5755,7 +6058,7 @@ func (x *HistoryPrefixResponse) String() string { func (*HistoryPrefixResponse) ProtoMessage() {} func (x *HistoryPrefixResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[51] + mi := &file_proto_v1_gateway_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5768,7 +6071,7 @@ func (x *HistoryPrefixResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPrefixResponse.ProtoReflect.Descriptor instead. func (*HistoryPrefixResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{51} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{54} } func (x *HistoryPrefixResponse) GetConversationId() string { @@ -5823,7 +6126,7 @@ type HistoryRenameRequest struct { func (x *HistoryRenameRequest) Reset() { *x = HistoryRenameRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[52] + mi := &file_proto_v1_gateway_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5835,7 +6138,7 @@ func (x *HistoryRenameRequest) String() string { func (*HistoryRenameRequest) ProtoMessage() {} func (x *HistoryRenameRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[52] + mi := &file_proto_v1_gateway_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5848,7 +6151,7 @@ func (x *HistoryRenameRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryRenameRequest.ProtoReflect.Descriptor instead. func (*HistoryRenameRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{52} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{55} } func (x *HistoryRenameRequest) GetConversationId() string { @@ -5874,7 +6177,7 @@ type HistoryRenameResponse struct { func (x *HistoryRenameResponse) Reset() { *x = HistoryRenameResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[53] + mi := &file_proto_v1_gateway_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5886,7 +6189,7 @@ func (x *HistoryRenameResponse) String() string { func (*HistoryRenameResponse) ProtoMessage() {} func (x *HistoryRenameResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[53] + mi := &file_proto_v1_gateway_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5899,7 +6202,7 @@ func (x *HistoryRenameResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryRenameResponse.ProtoReflect.Descriptor instead. func (*HistoryRenameResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{53} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{56} } func (x *HistoryRenameResponse) GetConversation() *ConversationSummary { @@ -5919,7 +6222,7 @@ type HistoryPinRequest struct { func (x *HistoryPinRequest) Reset() { *x = HistoryPinRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[54] + mi := &file_proto_v1_gateway_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5931,7 +6234,7 @@ func (x *HistoryPinRequest) String() string { func (*HistoryPinRequest) ProtoMessage() {} func (x *HistoryPinRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[54] + mi := &file_proto_v1_gateway_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5944,7 +6247,7 @@ func (x *HistoryPinRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPinRequest.ProtoReflect.Descriptor instead. func (*HistoryPinRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{54} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{57} } func (x *HistoryPinRequest) GetConversationId() string { @@ -5970,7 +6273,7 @@ type HistoryPinResponse struct { func (x *HistoryPinResponse) Reset() { *x = HistoryPinResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[55] + mi := &file_proto_v1_gateway_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5982,7 +6285,7 @@ func (x *HistoryPinResponse) String() string { func (*HistoryPinResponse) ProtoMessage() {} func (x *HistoryPinResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[55] + mi := &file_proto_v1_gateway_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5995,7 +6298,7 @@ func (x *HistoryPinResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryPinResponse.ProtoReflect.Descriptor instead. func (*HistoryPinResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{55} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{58} } func (x *HistoryPinResponse) GetConversation() *ConversationSummary { @@ -6019,7 +6322,7 @@ type HistoryShareStatus struct { func (x *HistoryShareStatus) Reset() { *x = HistoryShareStatus{} - mi := &file_proto_v1_gateway_proto_msgTypes[56] + mi := &file_proto_v1_gateway_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6031,7 +6334,7 @@ func (x *HistoryShareStatus) String() string { func (*HistoryShareStatus) ProtoMessage() {} func (x *HistoryShareStatus) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[56] + mi := &file_proto_v1_gateway_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6044,7 +6347,7 @@ func (x *HistoryShareStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareStatus.ProtoReflect.Descriptor instead. func (*HistoryShareStatus) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{56} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{59} } func (x *HistoryShareStatus) GetConversationId() string { @@ -6098,7 +6401,7 @@ type HistoryShareGetRequest struct { func (x *HistoryShareGetRequest) Reset() { *x = HistoryShareGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[57] + mi := &file_proto_v1_gateway_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6110,7 +6413,7 @@ func (x *HistoryShareGetRequest) String() string { func (*HistoryShareGetRequest) ProtoMessage() {} func (x *HistoryShareGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[57] + mi := &file_proto_v1_gateway_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6123,7 +6426,7 @@ func (x *HistoryShareGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareGetRequest.ProtoReflect.Descriptor instead. func (*HistoryShareGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{57} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{60} } func (x *HistoryShareGetRequest) GetConversationId() string { @@ -6142,7 +6445,7 @@ type HistoryShareGetResponse struct { func (x *HistoryShareGetResponse) Reset() { *x = HistoryShareGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[58] + mi := &file_proto_v1_gateway_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6154,7 +6457,7 @@ func (x *HistoryShareGetResponse) String() string { func (*HistoryShareGetResponse) ProtoMessage() {} func (x *HistoryShareGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[58] + mi := &file_proto_v1_gateway_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6167,7 +6470,7 @@ func (x *HistoryShareGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareGetResponse.ProtoReflect.Descriptor instead. func (*HistoryShareGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{58} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{61} } func (x *HistoryShareGetResponse) GetShare() *HistoryShareStatus { @@ -6188,7 +6491,7 @@ type HistoryShareSetRequest struct { func (x *HistoryShareSetRequest) Reset() { *x = HistoryShareSetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[59] + mi := &file_proto_v1_gateway_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6200,7 +6503,7 @@ func (x *HistoryShareSetRequest) String() string { func (*HistoryShareSetRequest) ProtoMessage() {} func (x *HistoryShareSetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[59] + mi := &file_proto_v1_gateway_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6213,7 +6516,7 @@ func (x *HistoryShareSetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareSetRequest.ProtoReflect.Descriptor instead. func (*HistoryShareSetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{59} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{62} } func (x *HistoryShareSetRequest) GetConversationId() string { @@ -6246,7 +6549,7 @@ type HistoryShareSetResponse struct { func (x *HistoryShareSetResponse) Reset() { *x = HistoryShareSetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[60] + mi := &file_proto_v1_gateway_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6258,7 +6561,7 @@ func (x *HistoryShareSetResponse) String() string { func (*HistoryShareSetResponse) ProtoMessage() {} func (x *HistoryShareSetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[60] + mi := &file_proto_v1_gateway_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6271,7 +6574,7 @@ func (x *HistoryShareSetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareSetResponse.ProtoReflect.Descriptor instead. func (*HistoryShareSetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{60} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{63} } func (x *HistoryShareSetResponse) GetShare() *HistoryShareStatus { @@ -6290,7 +6593,7 @@ type HistoryShareResolveRequest struct { func (x *HistoryShareResolveRequest) Reset() { *x = HistoryShareResolveRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[61] + mi := &file_proto_v1_gateway_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6302,7 +6605,7 @@ func (x *HistoryShareResolveRequest) String() string { func (*HistoryShareResolveRequest) ProtoMessage() {} func (x *HistoryShareResolveRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[61] + mi := &file_proto_v1_gateway_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6315,7 +6618,7 @@ func (x *HistoryShareResolveRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareResolveRequest.ProtoReflect.Descriptor instead. func (*HistoryShareResolveRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{61} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{64} } func (x *HistoryShareResolveRequest) GetToken() string { @@ -6338,7 +6641,7 @@ type HistoryShareResolveResponse struct { func (x *HistoryShareResolveResponse) Reset() { *x = HistoryShareResolveResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[62] + mi := &file_proto_v1_gateway_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6350,7 +6653,7 @@ func (x *HistoryShareResolveResponse) String() string { func (*HistoryShareResolveResponse) ProtoMessage() {} func (x *HistoryShareResolveResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[62] + mi := &file_proto_v1_gateway_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6363,7 +6666,7 @@ func (x *HistoryShareResolveResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryShareResolveResponse.ProtoReflect.Descriptor instead. func (*HistoryShareResolveResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{62} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{65} } func (x *HistoryShareResolveResponse) GetConversationId() string { @@ -6409,7 +6712,7 @@ type HistoryWorkdirsRequest struct { func (x *HistoryWorkdirsRequest) Reset() { *x = HistoryWorkdirsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[63] + mi := &file_proto_v1_gateway_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6421,7 +6724,7 @@ func (x *HistoryWorkdirsRequest) String() string { func (*HistoryWorkdirsRequest) ProtoMessage() {} func (x *HistoryWorkdirsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[63] + mi := &file_proto_v1_gateway_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6434,7 +6737,7 @@ func (x *HistoryWorkdirsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirsRequest.ProtoReflect.Descriptor instead. func (*HistoryWorkdirsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{63} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{66} } type HistoryWorkdirSummary struct { @@ -6448,7 +6751,7 @@ type HistoryWorkdirSummary struct { func (x *HistoryWorkdirSummary) Reset() { *x = HistoryWorkdirSummary{} - mi := &file_proto_v1_gateway_proto_msgTypes[64] + mi := &file_proto_v1_gateway_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6460,7 +6763,7 @@ func (x *HistoryWorkdirSummary) String() string { func (*HistoryWorkdirSummary) ProtoMessage() {} func (x *HistoryWorkdirSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[64] + mi := &file_proto_v1_gateway_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6473,7 +6776,7 @@ func (x *HistoryWorkdirSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirSummary.ProtoReflect.Descriptor instead. func (*HistoryWorkdirSummary) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{64} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{67} } func (x *HistoryWorkdirSummary) GetPath() string { @@ -6506,7 +6809,7 @@ type HistoryWorkdirsResponse struct { func (x *HistoryWorkdirsResponse) Reset() { *x = HistoryWorkdirsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[65] + mi := &file_proto_v1_gateway_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6518,7 +6821,7 @@ func (x *HistoryWorkdirsResponse) String() string { func (*HistoryWorkdirsResponse) ProtoMessage() {} func (x *HistoryWorkdirsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[65] + mi := &file_proto_v1_gateway_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6531,7 +6834,7 @@ func (x *HistoryWorkdirsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryWorkdirsResponse.ProtoReflect.Descriptor instead. func (*HistoryWorkdirsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{65} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{68} } func (x *HistoryWorkdirsResponse) GetWorkdirs() []*HistoryWorkdirSummary { @@ -6550,7 +6853,7 @@ type HistoryDeleteRequest struct { func (x *HistoryDeleteRequest) Reset() { *x = HistoryDeleteRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[66] + mi := &file_proto_v1_gateway_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6562,7 +6865,7 @@ func (x *HistoryDeleteRequest) String() string { func (*HistoryDeleteRequest) ProtoMessage() {} func (x *HistoryDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[66] + mi := &file_proto_v1_gateway_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6575,7 +6878,7 @@ func (x *HistoryDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryDeleteRequest.ProtoReflect.Descriptor instead. func (*HistoryDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{66} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{69} } func (x *HistoryDeleteRequest) GetConversationId() string { @@ -6593,7 +6896,7 @@ type HistoryDeleteResponse struct { func (x *HistoryDeleteResponse) Reset() { *x = HistoryDeleteResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[67] + mi := &file_proto_v1_gateway_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6605,7 +6908,7 @@ func (x *HistoryDeleteResponse) String() string { func (*HistoryDeleteResponse) ProtoMessage() {} func (x *HistoryDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[67] + mi := &file_proto_v1_gateway_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6618,7 +6921,7 @@ func (x *HistoryDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HistoryDeleteResponse.ProtoReflect.Descriptor instead. func (*HistoryDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{67} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{70} } type HistorySyncEvent struct { @@ -6632,7 +6935,7 @@ type HistorySyncEvent struct { func (x *HistorySyncEvent) Reset() { *x = HistorySyncEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[68] + mi := &file_proto_v1_gateway_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6644,7 +6947,7 @@ func (x *HistorySyncEvent) String() string { func (*HistorySyncEvent) ProtoMessage() {} func (x *HistorySyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[68] + mi := &file_proto_v1_gateway_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6657,7 +6960,7 @@ func (x *HistorySyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use HistorySyncEvent.ProtoReflect.Descriptor instead. func (*HistorySyncEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{68} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{71} } func (x *HistorySyncEvent) GetKind() string { @@ -6689,7 +6992,7 @@ type ProviderListRequest struct { func (x *ProviderListRequest) Reset() { *x = ProviderListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[69] + mi := &file_proto_v1_gateway_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6701,7 +7004,7 @@ func (x *ProviderListRequest) String() string { func (*ProviderListRequest) ProtoMessage() {} func (x *ProviderListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[69] + mi := &file_proto_v1_gateway_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6714,7 +7017,7 @@ func (x *ProviderListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderListRequest.ProtoReflect.Descriptor instead. func (*ProviderListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{69} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{72} } type ProviderListResponse struct { @@ -6726,7 +7029,7 @@ type ProviderListResponse struct { func (x *ProviderListResponse) Reset() { *x = ProviderListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[70] + mi := &file_proto_v1_gateway_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6738,7 +7041,7 @@ func (x *ProviderListResponse) String() string { func (*ProviderListResponse) ProtoMessage() {} func (x *ProviderListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[70] + mi := &file_proto_v1_gateway_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6751,7 +7054,7 @@ func (x *ProviderListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderListResponse.ProtoReflect.Descriptor instead. func (*ProviderListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{70} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{73} } func (x *ProviderListResponse) GetProvidersJson() string { @@ -6769,7 +7072,7 @@ type SettingsGetRequest struct { func (x *SettingsGetRequest) Reset() { *x = SettingsGetRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[71] + mi := &file_proto_v1_gateway_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6781,7 +7084,7 @@ func (x *SettingsGetRequest) String() string { func (*SettingsGetRequest) ProtoMessage() {} func (x *SettingsGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[71] + mi := &file_proto_v1_gateway_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6794,7 +7097,7 @@ func (x *SettingsGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsGetRequest.ProtoReflect.Descriptor instead. func (*SettingsGetRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{71} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{74} } type SettingsGetResponse struct { @@ -6806,7 +7109,7 @@ type SettingsGetResponse struct { func (x *SettingsGetResponse) Reset() { *x = SettingsGetResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[72] + mi := &file_proto_v1_gateway_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6818,7 +7121,7 @@ func (x *SettingsGetResponse) String() string { func (*SettingsGetResponse) ProtoMessage() {} func (x *SettingsGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[72] + mi := &file_proto_v1_gateway_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6831,7 +7134,7 @@ func (x *SettingsGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsGetResponse.ProtoReflect.Descriptor instead. func (*SettingsGetResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{72} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{75} } func (x *SettingsGetResponse) GetSettingsJson() string { @@ -6850,7 +7153,7 @@ type SettingsUpdateRequest struct { func (x *SettingsUpdateRequest) Reset() { *x = SettingsUpdateRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[73] + mi := &file_proto_v1_gateway_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6862,7 +7165,7 @@ func (x *SettingsUpdateRequest) String() string { func (*SettingsUpdateRequest) ProtoMessage() {} func (x *SettingsUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[73] + mi := &file_proto_v1_gateway_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6875,7 +7178,7 @@ func (x *SettingsUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsUpdateRequest.ProtoReflect.Descriptor instead. func (*SettingsUpdateRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{73} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{76} } func (x *SettingsUpdateRequest) GetSettingsJson() string { @@ -6895,7 +7198,7 @@ type SettingsUpdateResponse struct { func (x *SettingsUpdateResponse) Reset() { *x = SettingsUpdateResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[74] + mi := &file_proto_v1_gateway_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6907,7 +7210,7 @@ func (x *SettingsUpdateResponse) String() string { func (*SettingsUpdateResponse) ProtoMessage() {} func (x *SettingsUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[74] + mi := &file_proto_v1_gateway_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6920,7 +7223,7 @@ func (x *SettingsUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsUpdateResponse.ProtoReflect.Descriptor instead. func (*SettingsUpdateResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{74} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{77} } func (x *SettingsUpdateResponse) GetAccepted() bool { @@ -6947,7 +7250,7 @@ type SettingsResetSshKnownHostRequest struct { func (x *SettingsResetSshKnownHostRequest) Reset() { *x = SettingsResetSshKnownHostRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[75] + mi := &file_proto_v1_gateway_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6959,7 +7262,7 @@ func (x *SettingsResetSshKnownHostRequest) String() string { func (*SettingsResetSshKnownHostRequest) ProtoMessage() {} func (x *SettingsResetSshKnownHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[75] + mi := &file_proto_v1_gateway_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6972,7 +7275,7 @@ func (x *SettingsResetSshKnownHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsResetSshKnownHostRequest.ProtoReflect.Descriptor instead. func (*SettingsResetSshKnownHostRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{75} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{78} } func (x *SettingsResetSshKnownHostRequest) GetHost() string { @@ -6998,7 +7301,7 @@ type SettingsResetSshKnownHostResponse struct { func (x *SettingsResetSshKnownHostResponse) Reset() { *x = SettingsResetSshKnownHostResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[76] + mi := &file_proto_v1_gateway_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7010,7 +7313,7 @@ func (x *SettingsResetSshKnownHostResponse) String() string { func (*SettingsResetSshKnownHostResponse) ProtoMessage() {} func (x *SettingsResetSshKnownHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[76] + mi := &file_proto_v1_gateway_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7023,7 +7326,7 @@ func (x *SettingsResetSshKnownHostResponse) ProtoReflect() protoreflect.Message // Deprecated: Use SettingsResetSshKnownHostResponse.ProtoReflect.Descriptor instead. func (*SettingsResetSshKnownHostResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{76} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{79} } func (x *SettingsResetSshKnownHostResponse) GetDeleted() uint32 { @@ -7042,7 +7345,7 @@ type SettingsSyncEvent struct { func (x *SettingsSyncEvent) Reset() { *x = SettingsSyncEvent{} - mi := &file_proto_v1_gateway_proto_msgTypes[77] + mi := &file_proto_v1_gateway_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7054,7 +7357,7 @@ func (x *SettingsSyncEvent) String() string { func (*SettingsSyncEvent) ProtoMessage() {} func (x *SettingsSyncEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[77] + mi := &file_proto_v1_gateway_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7067,7 +7370,7 @@ func (x *SettingsSyncEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SettingsSyncEvent.ProtoReflect.Descriptor instead. func (*SettingsSyncEvent) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{77} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{80} } func (x *SettingsSyncEvent) GetSettingsJson() string { @@ -7085,7 +7388,7 @@ type SkillFilesListRequest struct { func (x *SkillFilesListRequest) Reset() { *x = SkillFilesListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[78] + mi := &file_proto_v1_gateway_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7097,7 +7400,7 @@ func (x *SkillFilesListRequest) String() string { func (*SkillFilesListRequest) ProtoMessage() {} func (x *SkillFilesListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[78] + mi := &file_proto_v1_gateway_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7110,7 +7413,7 @@ func (x *SkillFilesListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillFilesListRequest.ProtoReflect.Descriptor instead. func (*SkillFilesListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{78} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{81} } type SkillFilesListResponse struct { @@ -7124,7 +7427,7 @@ type SkillFilesListResponse struct { func (x *SkillFilesListResponse) Reset() { *x = SkillFilesListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[79] + mi := &file_proto_v1_gateway_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7136,7 +7439,7 @@ func (x *SkillFilesListResponse) String() string { func (*SkillFilesListResponse) ProtoMessage() {} func (x *SkillFilesListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[79] + mi := &file_proto_v1_gateway_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7149,7 +7452,7 @@ func (x *SkillFilesListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillFilesListResponse.ProtoReflect.Descriptor instead. func (*SkillFilesListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{79} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{82} } func (x *SkillFilesListResponse) GetRootDir() string { @@ -7182,7 +7485,7 @@ type SkillMetadataReadRequest struct { func (x *SkillMetadataReadRequest) Reset() { *x = SkillMetadataReadRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[80] + mi := &file_proto_v1_gateway_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7194,7 +7497,7 @@ func (x *SkillMetadataReadRequest) String() string { func (*SkillMetadataReadRequest) ProtoMessage() {} func (x *SkillMetadataReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[80] + mi := &file_proto_v1_gateway_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7207,7 +7510,7 @@ func (x *SkillMetadataReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillMetadataReadRequest.ProtoReflect.Descriptor instead. func (*SkillMetadataReadRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{80} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{83} } func (x *SkillMetadataReadRequest) GetPath() string { @@ -7227,7 +7530,7 @@ type SkillMetadataReadResponse struct { func (x *SkillMetadataReadResponse) Reset() { *x = SkillMetadataReadResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[81] + mi := &file_proto_v1_gateway_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7239,7 +7542,7 @@ func (x *SkillMetadataReadResponse) String() string { func (*SkillMetadataReadResponse) ProtoMessage() {} func (x *SkillMetadataReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[81] + mi := &file_proto_v1_gateway_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7252,7 +7555,7 @@ func (x *SkillMetadataReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillMetadataReadResponse.ProtoReflect.Descriptor instead. func (*SkillMetadataReadResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{81} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{84} } func (x *SkillMetadataReadResponse) GetName() string { @@ -7280,7 +7583,7 @@ type SkillTextReadRequest struct { func (x *SkillTextReadRequest) Reset() { *x = SkillTextReadRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[82] + mi := &file_proto_v1_gateway_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7292,7 +7595,7 @@ func (x *SkillTextReadRequest) String() string { func (*SkillTextReadRequest) ProtoMessage() {} func (x *SkillTextReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[82] + mi := &file_proto_v1_gateway_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7305,7 +7608,7 @@ func (x *SkillTextReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillTextReadRequest.ProtoReflect.Descriptor instead. func (*SkillTextReadRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{82} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{85} } func (x *SkillTextReadRequest) GetPath() string { @@ -7339,7 +7642,7 @@ type SkillTextReadResponse struct { func (x *SkillTextReadResponse) Reset() { *x = SkillTextReadResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[83] + mi := &file_proto_v1_gateway_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7351,7 +7654,7 @@ func (x *SkillTextReadResponse) String() string { func (*SkillTextReadResponse) ProtoMessage() {} func (x *SkillTextReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[83] + mi := &file_proto_v1_gateway_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7364,7 +7667,7 @@ func (x *SkillTextReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillTextReadResponse.ProtoReflect.Descriptor instead. func (*SkillTextReadResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{83} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{86} } func (x *SkillTextReadResponse) GetContent() string { @@ -7390,7 +7693,7 @@ type SkillManageRequest struct { func (x *SkillManageRequest) Reset() { *x = SkillManageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[84] + mi := &file_proto_v1_gateway_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7402,7 +7705,7 @@ func (x *SkillManageRequest) String() string { func (*SkillManageRequest) ProtoMessage() {} func (x *SkillManageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[84] + mi := &file_proto_v1_gateway_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7415,7 +7718,7 @@ func (x *SkillManageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillManageRequest.ProtoReflect.Descriptor instead. func (*SkillManageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{84} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{87} } func (x *SkillManageRequest) GetPayloadJson() string { @@ -7434,7 +7737,7 @@ type SkillManageResponse struct { func (x *SkillManageResponse) Reset() { *x = SkillManageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[85] + mi := &file_proto_v1_gateway_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7446,7 +7749,7 @@ func (x *SkillManageResponse) String() string { func (*SkillManageResponse) ProtoMessage() {} func (x *SkillManageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[85] + mi := &file_proto_v1_gateway_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7459,7 +7762,7 @@ func (x *SkillManageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SkillManageResponse.ProtoReflect.Descriptor instead. func (*SkillManageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{85} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{88} } func (x *SkillManageResponse) GetResultJson() string { @@ -7480,7 +7783,7 @@ type FileMentionListRequest struct { func (x *FileMentionListRequest) Reset() { *x = FileMentionListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[86] + mi := &file_proto_v1_gateway_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7492,7 +7795,7 @@ func (x *FileMentionListRequest) String() string { func (*FileMentionListRequest) ProtoMessage() {} func (x *FileMentionListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[86] + mi := &file_proto_v1_gateway_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7505,7 +7808,7 @@ func (x *FileMentionListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionListRequest.ProtoReflect.Descriptor instead. func (*FileMentionListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{86} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{89} } func (x *FileMentionListRequest) GetWorkdir() string { @@ -7539,7 +7842,7 @@ type FileMentionEntry struct { func (x *FileMentionEntry) Reset() { *x = FileMentionEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[87] + mi := &file_proto_v1_gateway_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7551,7 +7854,7 @@ func (x *FileMentionEntry) String() string { func (*FileMentionEntry) ProtoMessage() {} func (x *FileMentionEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[87] + mi := &file_proto_v1_gateway_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7564,7 +7867,7 @@ func (x *FileMentionEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionEntry.ProtoReflect.Descriptor instead. func (*FileMentionEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{87} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{90} } func (x *FileMentionEntry) GetPath() string { @@ -7591,7 +7894,7 @@ type FileMentionListResponse struct { func (x *FileMentionListResponse) Reset() { *x = FileMentionListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[88] + mi := &file_proto_v1_gateway_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7603,7 +7906,7 @@ func (x *FileMentionListResponse) String() string { func (*FileMentionListResponse) ProtoMessage() {} func (x *FileMentionListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[88] + mi := &file_proto_v1_gateway_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7616,7 +7919,7 @@ func (x *FileMentionListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FileMentionListResponse.ProtoReflect.Descriptor instead. func (*FileMentionListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{88} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{91} } func (x *FileMentionListResponse) GetEntries() []*FileMentionEntry { @@ -7645,7 +7948,7 @@ type FsRoot struct { func (x *FsRoot) Reset() { *x = FsRoot{} - mi := &file_proto_v1_gateway_proto_msgTypes[89] + mi := &file_proto_v1_gateway_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7657,7 +7960,7 @@ func (x *FsRoot) String() string { func (*FsRoot) ProtoMessage() {} func (x *FsRoot) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[89] + mi := &file_proto_v1_gateway_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7670,7 +7973,7 @@ func (x *FsRoot) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRoot.ProtoReflect.Descriptor instead. func (*FsRoot) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{89} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{92} } func (x *FsRoot) GetId() string { @@ -7709,7 +8012,7 @@ type FsRootsRequest struct { func (x *FsRootsRequest) Reset() { *x = FsRootsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[90] + mi := &file_proto_v1_gateway_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7721,7 +8024,7 @@ func (x *FsRootsRequest) String() string { func (*FsRootsRequest) ProtoMessage() {} func (x *FsRootsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[90] + mi := &file_proto_v1_gateway_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7734,7 +8037,7 @@ func (x *FsRootsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRootsRequest.ProtoReflect.Descriptor instead. func (*FsRootsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{90} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{93} } type FsRootsResponse struct { @@ -7746,7 +8049,7 @@ type FsRootsResponse struct { func (x *FsRootsResponse) Reset() { *x = FsRootsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[91] + mi := &file_proto_v1_gateway_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7758,7 +8061,7 @@ func (x *FsRootsResponse) String() string { func (*FsRootsResponse) ProtoMessage() {} func (x *FsRootsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[91] + mi := &file_proto_v1_gateway_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7771,7 +8074,7 @@ func (x *FsRootsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRootsResponse.ProtoReflect.Descriptor instead. func (*FsRootsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{91} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{94} } func (x *FsRootsResponse) GetRoots() []*FsRoot { @@ -7791,7 +8094,7 @@ type FsListDirsRequest struct { func (x *FsListDirsRequest) Reset() { *x = FsListDirsRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[92] + mi := &file_proto_v1_gateway_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7803,7 +8106,7 @@ func (x *FsListDirsRequest) String() string { func (*FsListDirsRequest) ProtoMessage() {} func (x *FsListDirsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[92] + mi := &file_proto_v1_gateway_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7816,7 +8119,7 @@ func (x *FsListDirsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListDirsRequest.ProtoReflect.Descriptor instead. func (*FsListDirsRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{92} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{95} } func (x *FsListDirsRequest) GetPath() string { @@ -7843,7 +8146,7 @@ type FsDirEntry struct { func (x *FsDirEntry) Reset() { *x = FsDirEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[93] + mi := &file_proto_v1_gateway_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7855,7 +8158,7 @@ func (x *FsDirEntry) String() string { func (*FsDirEntry) ProtoMessage() {} func (x *FsDirEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[93] + mi := &file_proto_v1_gateway_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7868,7 +8171,7 @@ func (x *FsDirEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDirEntry.ProtoReflect.Descriptor instead. func (*FsDirEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{93} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{96} } func (x *FsDirEntry) GetPath() string { @@ -7896,7 +8199,7 @@ type FsListDirsResponse struct { func (x *FsListDirsResponse) Reset() { *x = FsListDirsResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[94] + mi := &file_proto_v1_gateway_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7908,7 +8211,7 @@ func (x *FsListDirsResponse) String() string { func (*FsListDirsResponse) ProtoMessage() {} func (x *FsListDirsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[94] + mi := &file_proto_v1_gateway_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7921,7 +8224,7 @@ func (x *FsListDirsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListDirsResponse.ProtoReflect.Descriptor instead. func (*FsListDirsResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{94} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{97} } func (x *FsListDirsResponse) GetPath() string { @@ -7955,7 +8258,7 @@ type FsCreateProjectFolderRequest struct { func (x *FsCreateProjectFolderRequest) Reset() { *x = FsCreateProjectFolderRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[95] + mi := &file_proto_v1_gateway_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7967,7 +8270,7 @@ func (x *FsCreateProjectFolderRequest) String() string { func (*FsCreateProjectFolderRequest) ProtoMessage() {} func (x *FsCreateProjectFolderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[95] + mi := &file_proto_v1_gateway_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7980,7 +8283,7 @@ func (x *FsCreateProjectFolderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateProjectFolderRequest.ProtoReflect.Descriptor instead. func (*FsCreateProjectFolderRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{95} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{98} } func (x *FsCreateProjectFolderRequest) GetParent() string { @@ -8006,7 +8309,7 @@ type FsCreateProjectFolderResponse struct { func (x *FsCreateProjectFolderResponse) Reset() { *x = FsCreateProjectFolderResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[96] + mi := &file_proto_v1_gateway_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8018,7 +8321,7 @@ func (x *FsCreateProjectFolderResponse) String() string { func (*FsCreateProjectFolderResponse) ProtoMessage() {} func (x *FsCreateProjectFolderResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[96] + mi := &file_proto_v1_gateway_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8031,7 +8334,7 @@ func (x *FsCreateProjectFolderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateProjectFolderResponse.ProtoReflect.Descriptor instead. func (*FsCreateProjectFolderResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{96} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{99} } func (x *FsCreateProjectFolderResponse) GetPath() string { @@ -8054,7 +8357,7 @@ type FsListRequest struct { func (x *FsListRequest) Reset() { *x = FsListRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[97] + mi := &file_proto_v1_gateway_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8066,7 +8369,7 @@ func (x *FsListRequest) String() string { func (*FsListRequest) ProtoMessage() {} func (x *FsListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[97] + mi := &file_proto_v1_gateway_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8079,7 +8382,7 @@ func (x *FsListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListRequest.ProtoReflect.Descriptor instead. func (*FsListRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{97} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{100} } func (x *FsListRequest) GetWorkdir() string { @@ -8127,7 +8430,7 @@ type FsListEntry struct { func (x *FsListEntry) Reset() { *x = FsListEntry{} - mi := &file_proto_v1_gateway_proto_msgTypes[98] + mi := &file_proto_v1_gateway_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8139,7 +8442,7 @@ func (x *FsListEntry) String() string { func (*FsListEntry) ProtoMessage() {} func (x *FsListEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[98] + mi := &file_proto_v1_gateway_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8152,7 +8455,7 @@ func (x *FsListEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListEntry.ProtoReflect.Descriptor instead. func (*FsListEntry) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{98} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{101} } func (x *FsListEntry) GetPath() string { @@ -8185,7 +8488,7 @@ type FsListResponse struct { func (x *FsListResponse) Reset() { *x = FsListResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[99] + mi := &file_proto_v1_gateway_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8197,7 +8500,7 @@ func (x *FsListResponse) String() string { func (*FsListResponse) ProtoMessage() {} func (x *FsListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[99] + mi := &file_proto_v1_gateway_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8210,7 +8513,7 @@ func (x *FsListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsListResponse.ProtoReflect.Descriptor instead. func (*FsListResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{99} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{102} } func (x *FsListResponse) GetPath() string { @@ -8279,7 +8582,7 @@ type FsReadEditableTextRequest struct { func (x *FsReadEditableTextRequest) Reset() { *x = FsReadEditableTextRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[100] + mi := &file_proto_v1_gateway_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8291,7 +8594,7 @@ func (x *FsReadEditableTextRequest) String() string { func (*FsReadEditableTextRequest) ProtoMessage() {} func (x *FsReadEditableTextRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[100] + mi := &file_proto_v1_gateway_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8304,7 +8607,7 @@ func (x *FsReadEditableTextRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadEditableTextRequest.ProtoReflect.Descriptor instead. func (*FsReadEditableTextRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{100} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{103} } func (x *FsReadEditableTextRequest) GetWorkdir() string { @@ -8335,7 +8638,7 @@ type FsReadEditableTextResponse struct { func (x *FsReadEditableTextResponse) Reset() { *x = FsReadEditableTextResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[101] + mi := &file_proto_v1_gateway_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8347,7 +8650,7 @@ func (x *FsReadEditableTextResponse) String() string { func (*FsReadEditableTextResponse) ProtoMessage() {} func (x *FsReadEditableTextResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[101] + mi := &file_proto_v1_gateway_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8360,7 +8663,7 @@ func (x *FsReadEditableTextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadEditableTextResponse.ProtoReflect.Descriptor instead. func (*FsReadEditableTextResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{101} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{104} } func (x *FsReadEditableTextResponse) GetPath() string { @@ -8415,7 +8718,7 @@ type FsReadWorkspaceImageRequest struct { func (x *FsReadWorkspaceImageRequest) Reset() { *x = FsReadWorkspaceImageRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[102] + mi := &file_proto_v1_gateway_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8427,7 +8730,7 @@ func (x *FsReadWorkspaceImageRequest) String() string { func (*FsReadWorkspaceImageRequest) ProtoMessage() {} func (x *FsReadWorkspaceImageRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[102] + mi := &file_proto_v1_gateway_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8440,7 +8743,7 @@ func (x *FsReadWorkspaceImageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadWorkspaceImageRequest.ProtoReflect.Descriptor instead. func (*FsReadWorkspaceImageRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{102} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{105} } func (x *FsReadWorkspaceImageRequest) GetWorkdir() string { @@ -8471,7 +8774,7 @@ type FsReadWorkspaceImageResponse struct { func (x *FsReadWorkspaceImageResponse) Reset() { *x = FsReadWorkspaceImageResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[103] + mi := &file_proto_v1_gateway_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8483,7 +8786,7 @@ func (x *FsReadWorkspaceImageResponse) String() string { func (*FsReadWorkspaceImageResponse) ProtoMessage() {} func (x *FsReadWorkspaceImageResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[103] + mi := &file_proto_v1_gateway_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8496,7 +8799,7 @@ func (x *FsReadWorkspaceImageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsReadWorkspaceImageResponse.ProtoReflect.Descriptor instead. func (*FsReadWorkspaceImageResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{103} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{106} } func (x *FsReadWorkspaceImageResponse) GetPath() string { @@ -8557,7 +8860,7 @@ type FsWriteTextRequest struct { func (x *FsWriteTextRequest) Reset() { *x = FsWriteTextRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[104] + mi := &file_proto_v1_gateway_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8569,7 +8872,7 @@ func (x *FsWriteTextRequest) String() string { func (*FsWriteTextRequest) ProtoMessage() {} func (x *FsWriteTextRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[104] + mi := &file_proto_v1_gateway_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8582,7 +8885,7 @@ func (x *FsWriteTextRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsWriteTextRequest.ProtoReflect.Descriptor instead. func (*FsWriteTextRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{104} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{107} } func (x *FsWriteTextRequest) GetWorkdir() string { @@ -8656,7 +8959,7 @@ type FsWriteTextResponse struct { func (x *FsWriteTextResponse) Reset() { *x = FsWriteTextResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[105] + mi := &file_proto_v1_gateway_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8668,7 +8971,7 @@ func (x *FsWriteTextResponse) String() string { func (*FsWriteTextResponse) ProtoMessage() {} func (x *FsWriteTextResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[105] + mi := &file_proto_v1_gateway_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8681,7 +8984,7 @@ func (x *FsWriteTextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsWriteTextResponse.ProtoReflect.Descriptor instead. func (*FsWriteTextResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{105} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{108} } func (x *FsWriteTextResponse) GetPath() string { @@ -8743,7 +9046,7 @@ type FsCreateDirRequest struct { func (x *FsCreateDirRequest) Reset() { *x = FsCreateDirRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[106] + mi := &file_proto_v1_gateway_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8755,7 +9058,7 @@ func (x *FsCreateDirRequest) String() string { func (*FsCreateDirRequest) ProtoMessage() {} func (x *FsCreateDirRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[106] + mi := &file_proto_v1_gateway_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8768,7 +9071,7 @@ func (x *FsCreateDirRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateDirRequest.ProtoReflect.Descriptor instead. func (*FsCreateDirRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{106} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{109} } func (x *FsCreateDirRequest) GetWorkdir() string { @@ -8795,7 +9098,7 @@ type FsCreateDirResponse struct { func (x *FsCreateDirResponse) Reset() { *x = FsCreateDirResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[107] + mi := &file_proto_v1_gateway_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8807,7 +9110,7 @@ func (x *FsCreateDirResponse) String() string { func (*FsCreateDirResponse) ProtoMessage() {} func (x *FsCreateDirResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[107] + mi := &file_proto_v1_gateway_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8820,7 +9123,7 @@ func (x *FsCreateDirResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsCreateDirResponse.ProtoReflect.Descriptor instead. func (*FsCreateDirResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{107} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{110} } func (x *FsCreateDirResponse) GetPath() string { @@ -8848,7 +9151,7 @@ type FsRenameRequest struct { func (x *FsRenameRequest) Reset() { *x = FsRenameRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[108] + mi := &file_proto_v1_gateway_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8860,7 +9163,7 @@ func (x *FsRenameRequest) String() string { func (*FsRenameRequest) ProtoMessage() {} func (x *FsRenameRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[108] + mi := &file_proto_v1_gateway_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8873,7 +9176,7 @@ func (x *FsRenameRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRenameRequest.ProtoReflect.Descriptor instead. func (*FsRenameRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{108} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{111} } func (x *FsRenameRequest) GetWorkdir() string { @@ -8908,7 +9211,7 @@ type FsRenameResponse struct { func (x *FsRenameResponse) Reset() { *x = FsRenameResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[109] + mi := &file_proto_v1_gateway_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8920,7 +9223,7 @@ func (x *FsRenameResponse) String() string { func (*FsRenameResponse) ProtoMessage() {} func (x *FsRenameResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[109] + mi := &file_proto_v1_gateway_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8933,7 +9236,7 @@ func (x *FsRenameResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsRenameResponse.ProtoReflect.Descriptor instead. func (*FsRenameResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{109} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{112} } func (x *FsRenameResponse) GetFromPath() string { @@ -8967,7 +9270,7 @@ type FsDeleteRequest struct { func (x *FsDeleteRequest) Reset() { *x = FsDeleteRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[110] + mi := &file_proto_v1_gateway_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8979,7 +9282,7 @@ func (x *FsDeleteRequest) String() string { func (*FsDeleteRequest) ProtoMessage() {} func (x *FsDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[110] + mi := &file_proto_v1_gateway_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8992,7 +9295,7 @@ func (x *FsDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDeleteRequest.ProtoReflect.Descriptor instead. func (*FsDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{110} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{113} } func (x *FsDeleteRequest) GetWorkdir() string { @@ -9019,7 +9322,7 @@ type FsDeleteResponse struct { func (x *FsDeleteResponse) Reset() { *x = FsDeleteResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[111] + mi := &file_proto_v1_gateway_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9031,7 +9334,7 @@ func (x *FsDeleteResponse) String() string { func (*FsDeleteResponse) ProtoMessage() {} func (x *FsDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[111] + mi := &file_proto_v1_gateway_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9044,7 +9347,7 @@ func (x *FsDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FsDeleteResponse.ProtoReflect.Descriptor instead. func (*FsDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{111} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{114} } func (x *FsDeleteResponse) GetPath() string { @@ -9070,7 +9373,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} - mi := &file_proto_v1_gateway_proto_msgTypes[112] + mi := &file_proto_v1_gateway_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9082,7 +9385,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[112] + mi := &file_proto_v1_gateway_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9095,7 +9398,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{112} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{115} } func (x *PingRequest) GetTimestamp() int64 { @@ -9114,7 +9417,7 @@ type PongResponse struct { func (x *PongResponse) Reset() { *x = PongResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[113] + mi := &file_proto_v1_gateway_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9126,7 +9429,7 @@ func (x *PongResponse) String() string { func (*PongResponse) ProtoMessage() {} func (x *PongResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[113] + mi := &file_proto_v1_gateway_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9139,7 +9442,7 @@ func (x *PongResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PongResponse.ProtoReflect.Descriptor instead. func (*PongResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{113} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{116} } func (x *PongResponse) GetTimestamp() int64 { @@ -9159,7 +9462,7 @@ type ErrorResponse struct { func (x *ErrorResponse) Reset() { *x = ErrorResponse{} - mi := &file_proto_v1_gateway_proto_msgTypes[114] + mi := &file_proto_v1_gateway_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9171,7 +9474,7 @@ func (x *ErrorResponse) String() string { func (*ErrorResponse) ProtoMessage() {} func (x *ErrorResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_gateway_proto_msgTypes[114] + mi := &file_proto_v1_gateway_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9184,7 +9487,7 @@ func (x *ErrorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ErrorResponse.ProtoReflect.Descriptor instead. func (*ErrorResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_gateway_proto_rawDescGZIP(), []int{114} + return file_proto_v1_gateway_proto_rawDescGZIP(), []int{117} } func (x *ErrorResponse) GetCode() int32 { @@ -9214,7 +9517,7 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12\x1d\n" + "\n" + - "session_id\x18\x03 \x01(\tR\tsessionId\"\xfe\x1b\n" + + "session_id\x18\x03 \x01(\tR\tsessionId\"\xc7\x1c\n" + "\x0fGatewayEnvelope\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1c\n" + @@ -9265,8 +9568,10 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x0etunnel_control\x18C \x01(\v2*.liveagent.gateway.v1.TunnelControlRequestH\x00R\rtunnelControl\x12]\n" + "\x13tunnel_control_resp\x18D \x01(\v2+.liveagent.gateway.v1.TunnelControlResponseH\x00R\x11tunnelControlResp\x12F\n" + "\ftunnel_frame\x18E \x01(\v2!.liveagent.gateway.v1.TunnelFrameH\x00R\vtunnelFrame\x12z\n" + - "\x1dsettings_reset_ssh_known_host\x18H \x01(\v26.liveagent.gateway.v1.SettingsResetSshKnownHostRequestH\x00R\x19settingsResetSshKnownHostB\t\n" + - "\apayload\"\xce\"\n" + + "\x1dsettings_reset_ssh_known_host\x18H \x01(\v26.liveagent.gateway.v1.SettingsResetSshKnownHostRequestH\x00R\x19settingsResetSshKnownHost\x12G\n" + + "\n" + + "chat_queue\x18I \x01(\v2&.liveagent.gateway.v1.ChatQueueRequestH\x00R\tchatQueueB\t\n" + + "\apayload\"\xf3#\n" + "\rAgentEnvelope\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1c\n" + @@ -9315,7 +9620,9 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x1cfs_read_workspace_image_resp\x18B \x01(\v22.liveagent.gateway.v1.FsReadWorkspaceImageResponseH\x00R\x18fsReadWorkspaceImageResp\x12I\n" + "\rsftp_response\x18I \x01(\v2\".liveagent.gateway.v1.SftpResponseH\x00R\fsftpResponse\x12@\n" + "\n" + - "sftp_event\x18J \x01(\v2\x1f.liveagent.gateway.v1.SftpEventH\x00R\tsftpEvent\x12S\n" + + "sftp_event\x18J \x01(\v2\x1f.liveagent.gateway.v1.SftpEventH\x00R\tsftpEvent\x12Q\n" + + "\x0fchat_queue_resp\x18K \x01(\v2'.liveagent.gateway.v1.ChatQueueResponseH\x00R\rchatQueueResp\x12P\n" + + "\x10chat_queue_event\x18L \x01(\v2$.liveagent.gateway.v1.ChatQueueEventH\x00R\x0echatQueueEvent\x12S\n" + "\x0etunnel_control\x18C \x01(\v2*.liveagent.gateway.v1.TunnelControlRequestH\x00R\rtunnelControl\x12]\n" + "\x13tunnel_control_resp\x18D \x01(\v2+.liveagent.gateway.v1.TunnelControlResponseH\x00R\x11tunnelControlResp\x12F\n" + "\ftunnel_frame\x18E \x01(\v2!.liveagent.gateway.v1.TunnelFrameH\x00R\vtunnelFrame\x12K\n" + @@ -9610,7 +9917,7 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\vGitResponse\x12\x16\n" + "\x06action\x18\x01 \x01(\tR\x06action\x12\x1f\n" + "\vresult_json\x18\x02 \x01(\tR\n" + - "resultJson\"\xe6\x03\n" + + "resultJson\"\x89\x04\n" + "\vChatRequest\x12'\n" + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12N\n" + @@ -9620,7 +9927,9 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x15selected_system_tools\x18\x06 \x03(\tR\x13selectedSystemTools\x12M\n" + "\x0euploaded_files\x18\a \x03(\v2&.liveagent.gateway.v1.ChatUploadedFileR\ruploadedFiles\x12*\n" + "\x11client_request_id\x18\b \x01(\tR\x0fclientRequestId\x12T\n" + - "\x10runtime_controls\x18\t \x01(\v2).liveagent.gateway.v1.ChatRuntimeControlsR\x0fruntimeControls\"\xcf\x01\n" + + "\x10runtime_controls\x18\t \x01(\v2).liveagent.gateway.v1.ChatRuntimeControlsR\x0fruntimeControls\x12!\n" + + "\fqueue_policy\x18\n" + + " \x01(\tR\vqueuePolicy\"\xcf\x01\n" + "\x0eChatMessageRef\x12#\n" + "\rsegment_index\x18\x01 \x01(\x05R\fsegmentIndex\x12#\n" + "\rmessage_index\x18\x02 \x01(\x05R\fmessageIndex\x12\x1d\n" + @@ -9636,11 +9945,33 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x04type\x18\x01 \x01(\tR\x04type\x12;\n" + "\arequest\x18\x02 \x01(\v2!.liveagent.gateway.v1.ChatRequestR\arequest\x12N\n" + "\x10base_message_ref\x18\x03 \x01(\v2$.liveagent.gateway.v1.ChatMessageRefR\x0ebaseMessageRef\x12?\n" + - "\x06cancel\x18\x04 \x01(\v2'.liveagent.gateway.v1.CancelChatRequestR\x06cancel\"\x8f\x02\n" + + "\x06cancel\x18\x04 \x01(\v2'.liveagent.gateway.v1.CancelChatRequestR\x06cancel\"\x98\x02\n" + + "\x10ChatQueueRequest\x12\x16\n" + + "\x06action\x18\x01 \x01(\tR\x06action\x12'\n" + + "\x0fconversation_id\x18\x02 \x01(\tR\x0econversationId\x12\x17\n" + + "\aitem_id\x18\x03 \x01(\tR\x06itemId\x12\x1c\n" + + "\tdirection\x18\x04 \x01(\tR\tdirection\x12\x1a\n" + + "\brevision\x18\x05 \x01(\x04R\brevision\x12\x1d\n" + + "\n" + + "draft_json\x18\x06 \x01(\tR\tdraftJson\x12.\n" + + "\x13uploaded_files_json\x18\a \x01(\tR\x11uploadedFilesJson\x12!\n" + + "\frequest_json\x18\b \x01(\tR\vrequestJson\"\xc6\x01\n" + + "\x11ChatQueueResponse\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\x12#\n" + + "\rsnapshot_json\x18\x03 \x01(\tR\fsnapshotJson\x12\x1b\n" + + "\titem_json\x18\x04 \x01(\tR\bitemJson\x12\x1d\n" + + "\n" + + "error_code\x18\x05 \x01(\tR\terrorCode\x12\x1a\n" + + "\brevision\x18\x06 \x01(\x04R\brevision\"z\n" + + "\x0eChatQueueEvent\x12'\n" + + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12#\n" + + "\rsnapshot_json\x18\x02 \x01(\tR\fsnapshotJson\x12\x1a\n" + + "\brevision\x18\x03 \x01(\x04R\brevision\"\xa1\x02\n" + "\tChatEvent\x12A\n" + "\x04type\x18\x01 \x01(\x0e2-.liveagent.gateway.v1.ChatEvent.ChatEventTypeR\x04type\x12'\n" + "\x0fconversation_id\x18\x02 \x01(\tR\x0econversationId\x12\x12\n" + - "\x04data\x18\x03 \x01(\tR\x04data\"\x81\x01\n" + + "\x04data\x18\x03 \x01(\tR\x04data\"\x93\x01\n" + "\rChatEventType\x12\t\n" + "\x05TOKEN\x10\x00\x12\f\n" + "\bTHINKING\x10\x01\x12\r\n" + @@ -9649,7 +9980,8 @@ const file_proto_v1_gateway_proto_rawDesc = "" + "\x04DONE\x10\x04\x12\t\n" + "\x05ERROR\x10\x05\x12\x0f\n" + "\vTOOL_STATUS\x10\x06\x12\x11\n" + - "\rHOSTED_SEARCH\x10\a\"\x98\x02\n" + + "\rHOSTED_SEARCH\x10\a\x12\x10\n" + + "\fUSER_MESSAGE\x10\b\"\x98\x02\n" + "\x10ChatControlEvent\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12*\n" + @@ -9973,7 +10305,7 @@ func file_proto_v1_gateway_proto_rawDescGZIP() []byte { } var file_proto_v1_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_proto_v1_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 115) +var file_proto_v1_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 118) var file_proto_v1_gateway_proto_goTypes = []any{ (TunnelFrameKind)(0), // 0: liveagent.gateway.v1.TunnelFrameKind (ChatEvent_ChatEventType)(0), // 1: liveagent.gateway.v1.ChatEvent.ChatEventType @@ -10017,225 +10349,231 @@ var file_proto_v1_gateway_proto_goTypes = []any{ (*ChatMessageRef)(nil), // 39: liveagent.gateway.v1.ChatMessageRef (*CancelChatRequest)(nil), // 40: liveagent.gateway.v1.CancelChatRequest (*ChatCommandRequest)(nil), // 41: liveagent.gateway.v1.ChatCommandRequest - (*ChatEvent)(nil), // 42: liveagent.gateway.v1.ChatEvent - (*ChatControlEvent)(nil), // 43: liveagent.gateway.v1.ChatControlEvent - (*RuntimeStatusEvent)(nil), // 44: liveagent.gateway.v1.RuntimeStatusEvent - (*CronManageRequest)(nil), // 45: liveagent.gateway.v1.CronManageRequest - (*CronManageResponse)(nil), // 46: liveagent.gateway.v1.CronManageResponse - (*HistoryListRequest)(nil), // 47: liveagent.gateway.v1.HistoryListRequest - (*HistoryListResponse)(nil), // 48: liveagent.gateway.v1.HistoryListResponse - (*ConversationSummary)(nil), // 49: liveagent.gateway.v1.ConversationSummary - (*HistoryGetRequest)(nil), // 50: liveagent.gateway.v1.HistoryGetRequest - (*HistoryGetResponse)(nil), // 51: liveagent.gateway.v1.HistoryGetResponse - (*HistoryPrefixRequest)(nil), // 52: liveagent.gateway.v1.HistoryPrefixRequest - (*HistoryPrefixResponse)(nil), // 53: liveagent.gateway.v1.HistoryPrefixResponse - (*HistoryRenameRequest)(nil), // 54: liveagent.gateway.v1.HistoryRenameRequest - (*HistoryRenameResponse)(nil), // 55: liveagent.gateway.v1.HistoryRenameResponse - (*HistoryPinRequest)(nil), // 56: liveagent.gateway.v1.HistoryPinRequest - (*HistoryPinResponse)(nil), // 57: liveagent.gateway.v1.HistoryPinResponse - (*HistoryShareStatus)(nil), // 58: liveagent.gateway.v1.HistoryShareStatus - (*HistoryShareGetRequest)(nil), // 59: liveagent.gateway.v1.HistoryShareGetRequest - (*HistoryShareGetResponse)(nil), // 60: liveagent.gateway.v1.HistoryShareGetResponse - (*HistoryShareSetRequest)(nil), // 61: liveagent.gateway.v1.HistoryShareSetRequest - (*HistoryShareSetResponse)(nil), // 62: liveagent.gateway.v1.HistoryShareSetResponse - (*HistoryShareResolveRequest)(nil), // 63: liveagent.gateway.v1.HistoryShareResolveRequest - (*HistoryShareResolveResponse)(nil), // 64: liveagent.gateway.v1.HistoryShareResolveResponse - (*HistoryWorkdirsRequest)(nil), // 65: liveagent.gateway.v1.HistoryWorkdirsRequest - (*HistoryWorkdirSummary)(nil), // 66: liveagent.gateway.v1.HistoryWorkdirSummary - (*HistoryWorkdirsResponse)(nil), // 67: liveagent.gateway.v1.HistoryWorkdirsResponse - (*HistoryDeleteRequest)(nil), // 68: liveagent.gateway.v1.HistoryDeleteRequest - (*HistoryDeleteResponse)(nil), // 69: liveagent.gateway.v1.HistoryDeleteResponse - (*HistorySyncEvent)(nil), // 70: liveagent.gateway.v1.HistorySyncEvent - (*ProviderListRequest)(nil), // 71: liveagent.gateway.v1.ProviderListRequest - (*ProviderListResponse)(nil), // 72: liveagent.gateway.v1.ProviderListResponse - (*SettingsGetRequest)(nil), // 73: liveagent.gateway.v1.SettingsGetRequest - (*SettingsGetResponse)(nil), // 74: liveagent.gateway.v1.SettingsGetResponse - (*SettingsUpdateRequest)(nil), // 75: liveagent.gateway.v1.SettingsUpdateRequest - (*SettingsUpdateResponse)(nil), // 76: liveagent.gateway.v1.SettingsUpdateResponse - (*SettingsResetSshKnownHostRequest)(nil), // 77: liveagent.gateway.v1.SettingsResetSshKnownHostRequest - (*SettingsResetSshKnownHostResponse)(nil), // 78: liveagent.gateway.v1.SettingsResetSshKnownHostResponse - (*SettingsSyncEvent)(nil), // 79: liveagent.gateway.v1.SettingsSyncEvent - (*SkillFilesListRequest)(nil), // 80: liveagent.gateway.v1.SkillFilesListRequest - (*SkillFilesListResponse)(nil), // 81: liveagent.gateway.v1.SkillFilesListResponse - (*SkillMetadataReadRequest)(nil), // 82: liveagent.gateway.v1.SkillMetadataReadRequest - (*SkillMetadataReadResponse)(nil), // 83: liveagent.gateway.v1.SkillMetadataReadResponse - (*SkillTextReadRequest)(nil), // 84: liveagent.gateway.v1.SkillTextReadRequest - (*SkillTextReadResponse)(nil), // 85: liveagent.gateway.v1.SkillTextReadResponse - (*SkillManageRequest)(nil), // 86: liveagent.gateway.v1.SkillManageRequest - (*SkillManageResponse)(nil), // 87: liveagent.gateway.v1.SkillManageResponse - (*FileMentionListRequest)(nil), // 88: liveagent.gateway.v1.FileMentionListRequest - (*FileMentionEntry)(nil), // 89: liveagent.gateway.v1.FileMentionEntry - (*FileMentionListResponse)(nil), // 90: liveagent.gateway.v1.FileMentionListResponse - (*FsRoot)(nil), // 91: liveagent.gateway.v1.FsRoot - (*FsRootsRequest)(nil), // 92: liveagent.gateway.v1.FsRootsRequest - (*FsRootsResponse)(nil), // 93: liveagent.gateway.v1.FsRootsResponse - (*FsListDirsRequest)(nil), // 94: liveagent.gateway.v1.FsListDirsRequest - (*FsDirEntry)(nil), // 95: liveagent.gateway.v1.FsDirEntry - (*FsListDirsResponse)(nil), // 96: liveagent.gateway.v1.FsListDirsResponse - (*FsCreateProjectFolderRequest)(nil), // 97: liveagent.gateway.v1.FsCreateProjectFolderRequest - (*FsCreateProjectFolderResponse)(nil), // 98: liveagent.gateway.v1.FsCreateProjectFolderResponse - (*FsListRequest)(nil), // 99: liveagent.gateway.v1.FsListRequest - (*FsListEntry)(nil), // 100: liveagent.gateway.v1.FsListEntry - (*FsListResponse)(nil), // 101: liveagent.gateway.v1.FsListResponse - (*FsReadEditableTextRequest)(nil), // 102: liveagent.gateway.v1.FsReadEditableTextRequest - (*FsReadEditableTextResponse)(nil), // 103: liveagent.gateway.v1.FsReadEditableTextResponse - (*FsReadWorkspaceImageRequest)(nil), // 104: liveagent.gateway.v1.FsReadWorkspaceImageRequest - (*FsReadWorkspaceImageResponse)(nil), // 105: liveagent.gateway.v1.FsReadWorkspaceImageResponse - (*FsWriteTextRequest)(nil), // 106: liveagent.gateway.v1.FsWriteTextRequest - (*FsWriteTextResponse)(nil), // 107: liveagent.gateway.v1.FsWriteTextResponse - (*FsCreateDirRequest)(nil), // 108: liveagent.gateway.v1.FsCreateDirRequest - (*FsCreateDirResponse)(nil), // 109: liveagent.gateway.v1.FsCreateDirResponse - (*FsRenameRequest)(nil), // 110: liveagent.gateway.v1.FsRenameRequest - (*FsRenameResponse)(nil), // 111: liveagent.gateway.v1.FsRenameResponse - (*FsDeleteRequest)(nil), // 112: liveagent.gateway.v1.FsDeleteRequest - (*FsDeleteResponse)(nil), // 113: liveagent.gateway.v1.FsDeleteResponse - (*PingRequest)(nil), // 114: liveagent.gateway.v1.PingRequest - (*PongResponse)(nil), // 115: liveagent.gateway.v1.PongResponse - (*ErrorResponse)(nil), // 116: liveagent.gateway.v1.ErrorResponse + (*ChatQueueRequest)(nil), // 42: liveagent.gateway.v1.ChatQueueRequest + (*ChatQueueResponse)(nil), // 43: liveagent.gateway.v1.ChatQueueResponse + (*ChatQueueEvent)(nil), // 44: liveagent.gateway.v1.ChatQueueEvent + (*ChatEvent)(nil), // 45: liveagent.gateway.v1.ChatEvent + (*ChatControlEvent)(nil), // 46: liveagent.gateway.v1.ChatControlEvent + (*RuntimeStatusEvent)(nil), // 47: liveagent.gateway.v1.RuntimeStatusEvent + (*CronManageRequest)(nil), // 48: liveagent.gateway.v1.CronManageRequest + (*CronManageResponse)(nil), // 49: liveagent.gateway.v1.CronManageResponse + (*HistoryListRequest)(nil), // 50: liveagent.gateway.v1.HistoryListRequest + (*HistoryListResponse)(nil), // 51: liveagent.gateway.v1.HistoryListResponse + (*ConversationSummary)(nil), // 52: liveagent.gateway.v1.ConversationSummary + (*HistoryGetRequest)(nil), // 53: liveagent.gateway.v1.HistoryGetRequest + (*HistoryGetResponse)(nil), // 54: liveagent.gateway.v1.HistoryGetResponse + (*HistoryPrefixRequest)(nil), // 55: liveagent.gateway.v1.HistoryPrefixRequest + (*HistoryPrefixResponse)(nil), // 56: liveagent.gateway.v1.HistoryPrefixResponse + (*HistoryRenameRequest)(nil), // 57: liveagent.gateway.v1.HistoryRenameRequest + (*HistoryRenameResponse)(nil), // 58: liveagent.gateway.v1.HistoryRenameResponse + (*HistoryPinRequest)(nil), // 59: liveagent.gateway.v1.HistoryPinRequest + (*HistoryPinResponse)(nil), // 60: liveagent.gateway.v1.HistoryPinResponse + (*HistoryShareStatus)(nil), // 61: liveagent.gateway.v1.HistoryShareStatus + (*HistoryShareGetRequest)(nil), // 62: liveagent.gateway.v1.HistoryShareGetRequest + (*HistoryShareGetResponse)(nil), // 63: liveagent.gateway.v1.HistoryShareGetResponse + (*HistoryShareSetRequest)(nil), // 64: liveagent.gateway.v1.HistoryShareSetRequest + (*HistoryShareSetResponse)(nil), // 65: liveagent.gateway.v1.HistoryShareSetResponse + (*HistoryShareResolveRequest)(nil), // 66: liveagent.gateway.v1.HistoryShareResolveRequest + (*HistoryShareResolveResponse)(nil), // 67: liveagent.gateway.v1.HistoryShareResolveResponse + (*HistoryWorkdirsRequest)(nil), // 68: liveagent.gateway.v1.HistoryWorkdirsRequest + (*HistoryWorkdirSummary)(nil), // 69: liveagent.gateway.v1.HistoryWorkdirSummary + (*HistoryWorkdirsResponse)(nil), // 70: liveagent.gateway.v1.HistoryWorkdirsResponse + (*HistoryDeleteRequest)(nil), // 71: liveagent.gateway.v1.HistoryDeleteRequest + (*HistoryDeleteResponse)(nil), // 72: liveagent.gateway.v1.HistoryDeleteResponse + (*HistorySyncEvent)(nil), // 73: liveagent.gateway.v1.HistorySyncEvent + (*ProviderListRequest)(nil), // 74: liveagent.gateway.v1.ProviderListRequest + (*ProviderListResponse)(nil), // 75: liveagent.gateway.v1.ProviderListResponse + (*SettingsGetRequest)(nil), // 76: liveagent.gateway.v1.SettingsGetRequest + (*SettingsGetResponse)(nil), // 77: liveagent.gateway.v1.SettingsGetResponse + (*SettingsUpdateRequest)(nil), // 78: liveagent.gateway.v1.SettingsUpdateRequest + (*SettingsUpdateResponse)(nil), // 79: liveagent.gateway.v1.SettingsUpdateResponse + (*SettingsResetSshKnownHostRequest)(nil), // 80: liveagent.gateway.v1.SettingsResetSshKnownHostRequest + (*SettingsResetSshKnownHostResponse)(nil), // 81: liveagent.gateway.v1.SettingsResetSshKnownHostResponse + (*SettingsSyncEvent)(nil), // 82: liveagent.gateway.v1.SettingsSyncEvent + (*SkillFilesListRequest)(nil), // 83: liveagent.gateway.v1.SkillFilesListRequest + (*SkillFilesListResponse)(nil), // 84: liveagent.gateway.v1.SkillFilesListResponse + (*SkillMetadataReadRequest)(nil), // 85: liveagent.gateway.v1.SkillMetadataReadRequest + (*SkillMetadataReadResponse)(nil), // 86: liveagent.gateway.v1.SkillMetadataReadResponse + (*SkillTextReadRequest)(nil), // 87: liveagent.gateway.v1.SkillTextReadRequest + (*SkillTextReadResponse)(nil), // 88: liveagent.gateway.v1.SkillTextReadResponse + (*SkillManageRequest)(nil), // 89: liveagent.gateway.v1.SkillManageRequest + (*SkillManageResponse)(nil), // 90: liveagent.gateway.v1.SkillManageResponse + (*FileMentionListRequest)(nil), // 91: liveagent.gateway.v1.FileMentionListRequest + (*FileMentionEntry)(nil), // 92: liveagent.gateway.v1.FileMentionEntry + (*FileMentionListResponse)(nil), // 93: liveagent.gateway.v1.FileMentionListResponse + (*FsRoot)(nil), // 94: liveagent.gateway.v1.FsRoot + (*FsRootsRequest)(nil), // 95: liveagent.gateway.v1.FsRootsRequest + (*FsRootsResponse)(nil), // 96: liveagent.gateway.v1.FsRootsResponse + (*FsListDirsRequest)(nil), // 97: liveagent.gateway.v1.FsListDirsRequest + (*FsDirEntry)(nil), // 98: liveagent.gateway.v1.FsDirEntry + (*FsListDirsResponse)(nil), // 99: liveagent.gateway.v1.FsListDirsResponse + (*FsCreateProjectFolderRequest)(nil), // 100: liveagent.gateway.v1.FsCreateProjectFolderRequest + (*FsCreateProjectFolderResponse)(nil), // 101: liveagent.gateway.v1.FsCreateProjectFolderResponse + (*FsListRequest)(nil), // 102: liveagent.gateway.v1.FsListRequest + (*FsListEntry)(nil), // 103: liveagent.gateway.v1.FsListEntry + (*FsListResponse)(nil), // 104: liveagent.gateway.v1.FsListResponse + (*FsReadEditableTextRequest)(nil), // 105: liveagent.gateway.v1.FsReadEditableTextRequest + (*FsReadEditableTextResponse)(nil), // 106: liveagent.gateway.v1.FsReadEditableTextResponse + (*FsReadWorkspaceImageRequest)(nil), // 107: liveagent.gateway.v1.FsReadWorkspaceImageRequest + (*FsReadWorkspaceImageResponse)(nil), // 108: liveagent.gateway.v1.FsReadWorkspaceImageResponse + (*FsWriteTextRequest)(nil), // 109: liveagent.gateway.v1.FsWriteTextRequest + (*FsWriteTextResponse)(nil), // 110: liveagent.gateway.v1.FsWriteTextResponse + (*FsCreateDirRequest)(nil), // 111: liveagent.gateway.v1.FsCreateDirRequest + (*FsCreateDirResponse)(nil), // 112: liveagent.gateway.v1.FsCreateDirResponse + (*FsRenameRequest)(nil), // 113: liveagent.gateway.v1.FsRenameRequest + (*FsRenameResponse)(nil), // 114: liveagent.gateway.v1.FsRenameResponse + (*FsDeleteRequest)(nil), // 115: liveagent.gateway.v1.FsDeleteRequest + (*FsDeleteResponse)(nil), // 116: liveagent.gateway.v1.FsDeleteResponse + (*PingRequest)(nil), // 117: liveagent.gateway.v1.PingRequest + (*PongResponse)(nil), // 118: liveagent.gateway.v1.PongResponse + (*ErrorResponse)(nil), // 119: liveagent.gateway.v1.ErrorResponse } var file_proto_v1_gateway_proto_depIdxs = []int32{ 41, // 0: liveagent.gateway.v1.GatewayEnvelope.chat_command:type_name -> liveagent.gateway.v1.ChatCommandRequest - 45, // 1: liveagent.gateway.v1.GatewayEnvelope.cron_manage:type_name -> liveagent.gateway.v1.CronManageRequest - 47, // 2: liveagent.gateway.v1.GatewayEnvelope.history_list:type_name -> liveagent.gateway.v1.HistoryListRequest - 50, // 3: liveagent.gateway.v1.GatewayEnvelope.history_get:type_name -> liveagent.gateway.v1.HistoryGetRequest - 54, // 4: liveagent.gateway.v1.GatewayEnvelope.history_rename:type_name -> liveagent.gateway.v1.HistoryRenameRequest - 68, // 5: liveagent.gateway.v1.GatewayEnvelope.history_delete:type_name -> liveagent.gateway.v1.HistoryDeleteRequest - 52, // 6: liveagent.gateway.v1.GatewayEnvelope.history_prefix:type_name -> liveagent.gateway.v1.HistoryPrefixRequest - 56, // 7: liveagent.gateway.v1.GatewayEnvelope.history_pin:type_name -> liveagent.gateway.v1.HistoryPinRequest - 59, // 8: liveagent.gateway.v1.GatewayEnvelope.history_share_get:type_name -> liveagent.gateway.v1.HistoryShareGetRequest - 61, // 9: liveagent.gateway.v1.GatewayEnvelope.history_share_set:type_name -> liveagent.gateway.v1.HistoryShareSetRequest - 63, // 10: liveagent.gateway.v1.GatewayEnvelope.history_share_resolve:type_name -> liveagent.gateway.v1.HistoryShareResolveRequest - 65, // 11: liveagent.gateway.v1.GatewayEnvelope.history_workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirsRequest - 71, // 12: liveagent.gateway.v1.GatewayEnvelope.provider_list:type_name -> liveagent.gateway.v1.ProviderListRequest - 73, // 13: liveagent.gateway.v1.GatewayEnvelope.settings_get:type_name -> liveagent.gateway.v1.SettingsGetRequest - 75, // 14: liveagent.gateway.v1.GatewayEnvelope.settings_update:type_name -> liveagent.gateway.v1.SettingsUpdateRequest - 80, // 15: liveagent.gateway.v1.GatewayEnvelope.skill_files_list:type_name -> liveagent.gateway.v1.SkillFilesListRequest - 82, // 16: liveagent.gateway.v1.GatewayEnvelope.skill_metadata_read:type_name -> liveagent.gateway.v1.SkillMetadataReadRequest - 84, // 17: liveagent.gateway.v1.GatewayEnvelope.skill_text_read:type_name -> liveagent.gateway.v1.SkillTextReadRequest - 88, // 18: liveagent.gateway.v1.GatewayEnvelope.file_mention_list:type_name -> liveagent.gateway.v1.FileMentionListRequest + 48, // 1: liveagent.gateway.v1.GatewayEnvelope.cron_manage:type_name -> liveagent.gateway.v1.CronManageRequest + 50, // 2: liveagent.gateway.v1.GatewayEnvelope.history_list:type_name -> liveagent.gateway.v1.HistoryListRequest + 53, // 3: liveagent.gateway.v1.GatewayEnvelope.history_get:type_name -> liveagent.gateway.v1.HistoryGetRequest + 57, // 4: liveagent.gateway.v1.GatewayEnvelope.history_rename:type_name -> liveagent.gateway.v1.HistoryRenameRequest + 71, // 5: liveagent.gateway.v1.GatewayEnvelope.history_delete:type_name -> liveagent.gateway.v1.HistoryDeleteRequest + 55, // 6: liveagent.gateway.v1.GatewayEnvelope.history_prefix:type_name -> liveagent.gateway.v1.HistoryPrefixRequest + 59, // 7: liveagent.gateway.v1.GatewayEnvelope.history_pin:type_name -> liveagent.gateway.v1.HistoryPinRequest + 62, // 8: liveagent.gateway.v1.GatewayEnvelope.history_share_get:type_name -> liveagent.gateway.v1.HistoryShareGetRequest + 64, // 9: liveagent.gateway.v1.GatewayEnvelope.history_share_set:type_name -> liveagent.gateway.v1.HistoryShareSetRequest + 66, // 10: liveagent.gateway.v1.GatewayEnvelope.history_share_resolve:type_name -> liveagent.gateway.v1.HistoryShareResolveRequest + 68, // 11: liveagent.gateway.v1.GatewayEnvelope.history_workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirsRequest + 74, // 12: liveagent.gateway.v1.GatewayEnvelope.provider_list:type_name -> liveagent.gateway.v1.ProviderListRequest + 76, // 13: liveagent.gateway.v1.GatewayEnvelope.settings_get:type_name -> liveagent.gateway.v1.SettingsGetRequest + 78, // 14: liveagent.gateway.v1.GatewayEnvelope.settings_update:type_name -> liveagent.gateway.v1.SettingsUpdateRequest + 83, // 15: liveagent.gateway.v1.GatewayEnvelope.skill_files_list:type_name -> liveagent.gateway.v1.SkillFilesListRequest + 85, // 16: liveagent.gateway.v1.GatewayEnvelope.skill_metadata_read:type_name -> liveagent.gateway.v1.SkillMetadataReadRequest + 87, // 17: liveagent.gateway.v1.GatewayEnvelope.skill_text_read:type_name -> liveagent.gateway.v1.SkillTextReadRequest + 91, // 18: liveagent.gateway.v1.GatewayEnvelope.file_mention_list:type_name -> liveagent.gateway.v1.FileMentionListRequest 10, // 19: liveagent.gateway.v1.GatewayEnvelope.upload_readable_files:type_name -> liveagent.gateway.v1.UploadReadableFilesRequest - 92, // 20: liveagent.gateway.v1.GatewayEnvelope.fs_roots:type_name -> liveagent.gateway.v1.FsRootsRequest - 94, // 21: liveagent.gateway.v1.GatewayEnvelope.fs_list_dirs:type_name -> liveagent.gateway.v1.FsListDirsRequest - 114, // 22: liveagent.gateway.v1.GatewayEnvelope.ping:type_name -> liveagent.gateway.v1.PingRequest + 95, // 20: liveagent.gateway.v1.GatewayEnvelope.fs_roots:type_name -> liveagent.gateway.v1.FsRootsRequest + 97, // 21: liveagent.gateway.v1.GatewayEnvelope.fs_list_dirs:type_name -> liveagent.gateway.v1.FsListDirsRequest + 117, // 22: liveagent.gateway.v1.GatewayEnvelope.ping:type_name -> liveagent.gateway.v1.PingRequest 12, // 23: liveagent.gateway.v1.GatewayEnvelope.uploaded_image_preview:type_name -> liveagent.gateway.v1.UploadedImagePreviewRequest 19, // 24: liveagent.gateway.v1.GatewayEnvelope.memory_manage:type_name -> liveagent.gateway.v1.MemoryManageRequest - 86, // 25: liveagent.gateway.v1.GatewayEnvelope.skill_manage:type_name -> liveagent.gateway.v1.SkillManageRequest - 97, // 26: liveagent.gateway.v1.GatewayEnvelope.fs_create_project_folder:type_name -> liveagent.gateway.v1.FsCreateProjectFolderRequest + 89, // 25: liveagent.gateway.v1.GatewayEnvelope.skill_manage:type_name -> liveagent.gateway.v1.SkillManageRequest + 100, // 26: liveagent.gateway.v1.GatewayEnvelope.fs_create_project_folder:type_name -> liveagent.gateway.v1.FsCreateProjectFolderRequest 21, // 27: liveagent.gateway.v1.GatewayEnvelope.terminal_request:type_name -> liveagent.gateway.v1.TerminalRequest - 99, // 28: liveagent.gateway.v1.GatewayEnvelope.fs_list:type_name -> liveagent.gateway.v1.FsListRequest - 106, // 29: liveagent.gateway.v1.GatewayEnvelope.fs_write_text:type_name -> liveagent.gateway.v1.FsWriteTextRequest - 108, // 30: liveagent.gateway.v1.GatewayEnvelope.fs_create_dir:type_name -> liveagent.gateway.v1.FsCreateDirRequest - 110, // 31: liveagent.gateway.v1.GatewayEnvelope.fs_rename:type_name -> liveagent.gateway.v1.FsRenameRequest - 112, // 32: liveagent.gateway.v1.GatewayEnvelope.fs_delete:type_name -> liveagent.gateway.v1.FsDeleteRequest + 102, // 28: liveagent.gateway.v1.GatewayEnvelope.fs_list:type_name -> liveagent.gateway.v1.FsListRequest + 109, // 29: liveagent.gateway.v1.GatewayEnvelope.fs_write_text:type_name -> liveagent.gateway.v1.FsWriteTextRequest + 111, // 30: liveagent.gateway.v1.GatewayEnvelope.fs_create_dir:type_name -> liveagent.gateway.v1.FsCreateDirRequest + 113, // 31: liveagent.gateway.v1.GatewayEnvelope.fs_rename:type_name -> liveagent.gateway.v1.FsRenameRequest + 115, // 32: liveagent.gateway.v1.GatewayEnvelope.fs_delete:type_name -> liveagent.gateway.v1.FsDeleteRequest 36, // 33: liveagent.gateway.v1.GatewayEnvelope.git_request:type_name -> liveagent.gateway.v1.GitRequest - 102, // 34: liveagent.gateway.v1.GatewayEnvelope.fs_read_editable_text:type_name -> liveagent.gateway.v1.FsReadEditableTextRequest - 104, // 35: liveagent.gateway.v1.GatewayEnvelope.fs_read_workspace_image:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageRequest + 105, // 34: liveagent.gateway.v1.GatewayEnvelope.fs_read_editable_text:type_name -> liveagent.gateway.v1.FsReadEditableTextRequest + 107, // 35: liveagent.gateway.v1.GatewayEnvelope.fs_read_workspace_image:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageRequest 24, // 36: liveagent.gateway.v1.GatewayEnvelope.sftp_request:type_name -> liveagent.gateway.v1.SftpRequest 14, // 37: liveagent.gateway.v1.GatewayEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest 15, // 38: liveagent.gateway.v1.GatewayEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse 18, // 39: liveagent.gateway.v1.GatewayEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame - 77, // 40: liveagent.gateway.v1.GatewayEnvelope.settings_reset_ssh_known_host:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostRequest - 42, // 41: liveagent.gateway.v1.AgentEnvelope.chat_event:type_name -> liveagent.gateway.v1.ChatEvent - 46, // 42: liveagent.gateway.v1.AgentEnvelope.cron_manage_resp:type_name -> liveagent.gateway.v1.CronManageResponse - 48, // 43: liveagent.gateway.v1.AgentEnvelope.history_list_resp:type_name -> liveagent.gateway.v1.HistoryListResponse - 51, // 44: liveagent.gateway.v1.AgentEnvelope.history_get_resp:type_name -> liveagent.gateway.v1.HistoryGetResponse - 55, // 45: liveagent.gateway.v1.AgentEnvelope.history_rename_resp:type_name -> liveagent.gateway.v1.HistoryRenameResponse - 69, // 46: liveagent.gateway.v1.AgentEnvelope.history_delete_resp:type_name -> liveagent.gateway.v1.HistoryDeleteResponse - 70, // 47: liveagent.gateway.v1.AgentEnvelope.history_sync:type_name -> liveagent.gateway.v1.HistorySyncEvent - 53, // 48: liveagent.gateway.v1.AgentEnvelope.history_prefix_resp:type_name -> liveagent.gateway.v1.HistoryPrefixResponse - 57, // 49: liveagent.gateway.v1.AgentEnvelope.history_pin_resp:type_name -> liveagent.gateway.v1.HistoryPinResponse - 60, // 50: liveagent.gateway.v1.AgentEnvelope.history_share_get_resp:type_name -> liveagent.gateway.v1.HistoryShareGetResponse - 62, // 51: liveagent.gateway.v1.AgentEnvelope.history_share_set_resp:type_name -> liveagent.gateway.v1.HistoryShareSetResponse - 64, // 52: liveagent.gateway.v1.AgentEnvelope.history_share_resolve_resp:type_name -> liveagent.gateway.v1.HistoryShareResolveResponse - 67, // 53: liveagent.gateway.v1.AgentEnvelope.history_workdirs_resp:type_name -> liveagent.gateway.v1.HistoryWorkdirsResponse - 72, // 54: liveagent.gateway.v1.AgentEnvelope.provider_list_resp:type_name -> liveagent.gateway.v1.ProviderListResponse - 74, // 55: liveagent.gateway.v1.AgentEnvelope.settings_get_resp:type_name -> liveagent.gateway.v1.SettingsGetResponse - 76, // 56: liveagent.gateway.v1.AgentEnvelope.settings_update_resp:type_name -> liveagent.gateway.v1.SettingsUpdateResponse - 79, // 57: liveagent.gateway.v1.AgentEnvelope.settings_sync:type_name -> liveagent.gateway.v1.SettingsSyncEvent - 81, // 58: liveagent.gateway.v1.AgentEnvelope.skill_files_list_resp:type_name -> liveagent.gateway.v1.SkillFilesListResponse - 83, // 59: liveagent.gateway.v1.AgentEnvelope.skill_metadata_read_resp:type_name -> liveagent.gateway.v1.SkillMetadataReadResponse - 85, // 60: liveagent.gateway.v1.AgentEnvelope.skill_text_read_resp:type_name -> liveagent.gateway.v1.SkillTextReadResponse - 90, // 61: liveagent.gateway.v1.AgentEnvelope.file_mention_list_resp:type_name -> liveagent.gateway.v1.FileMentionListResponse - 11, // 62: liveagent.gateway.v1.AgentEnvelope.upload_readable_files_resp:type_name -> liveagent.gateway.v1.UploadReadableFilesResponse - 93, // 63: liveagent.gateway.v1.AgentEnvelope.fs_roots_resp:type_name -> liveagent.gateway.v1.FsRootsResponse - 115, // 64: liveagent.gateway.v1.AgentEnvelope.pong:type_name -> liveagent.gateway.v1.PongResponse - 96, // 65: liveagent.gateway.v1.AgentEnvelope.fs_list_dirs_resp:type_name -> liveagent.gateway.v1.FsListDirsResponse - 13, // 66: liveagent.gateway.v1.AgentEnvelope.uploaded_image_preview_resp:type_name -> liveagent.gateway.v1.UploadedImagePreviewResponse - 20, // 67: liveagent.gateway.v1.AgentEnvelope.memory_manage_resp:type_name -> liveagent.gateway.v1.MemoryManageResponse - 87, // 68: liveagent.gateway.v1.AgentEnvelope.skill_manage_resp:type_name -> liveagent.gateway.v1.SkillManageResponse - 98, // 69: liveagent.gateway.v1.AgentEnvelope.fs_create_project_folder_resp:type_name -> liveagent.gateway.v1.FsCreateProjectFolderResponse - 33, // 70: liveagent.gateway.v1.AgentEnvelope.terminal_response:type_name -> liveagent.gateway.v1.TerminalResponse - 34, // 71: liveagent.gateway.v1.AgentEnvelope.terminal_event:type_name -> liveagent.gateway.v1.TerminalEvent - 101, // 72: liveagent.gateway.v1.AgentEnvelope.fs_list_resp:type_name -> liveagent.gateway.v1.FsListResponse - 107, // 73: liveagent.gateway.v1.AgentEnvelope.fs_write_text_resp:type_name -> liveagent.gateway.v1.FsWriteTextResponse - 109, // 74: liveagent.gateway.v1.AgentEnvelope.fs_create_dir_resp:type_name -> liveagent.gateway.v1.FsCreateDirResponse - 111, // 75: liveagent.gateway.v1.AgentEnvelope.fs_rename_resp:type_name -> liveagent.gateway.v1.FsRenameResponse - 113, // 76: liveagent.gateway.v1.AgentEnvelope.fs_delete_resp:type_name -> liveagent.gateway.v1.FsDeleteResponse - 37, // 77: liveagent.gateway.v1.AgentEnvelope.git_response:type_name -> liveagent.gateway.v1.GitResponse - 103, // 78: liveagent.gateway.v1.AgentEnvelope.fs_read_editable_text_resp:type_name -> liveagent.gateway.v1.FsReadEditableTextResponse - 105, // 79: liveagent.gateway.v1.AgentEnvelope.fs_read_workspace_image_resp:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageResponse - 27, // 80: liveagent.gateway.v1.AgentEnvelope.sftp_response:type_name -> liveagent.gateway.v1.SftpResponse - 28, // 81: liveagent.gateway.v1.AgentEnvelope.sftp_event:type_name -> liveagent.gateway.v1.SftpEvent - 14, // 82: liveagent.gateway.v1.AgentEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest - 15, // 83: liveagent.gateway.v1.AgentEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse - 18, // 84: liveagent.gateway.v1.AgentEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame - 43, // 85: liveagent.gateway.v1.AgentEnvelope.chat_control:type_name -> liveagent.gateway.v1.ChatControlEvent - 44, // 86: liveagent.gateway.v1.AgentEnvelope.runtime_status:type_name -> liveagent.gateway.v1.RuntimeStatusEvent - 78, // 87: liveagent.gateway.v1.AgentEnvelope.settings_reset_ssh_known_host_resp:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostResponse - 116, // 88: liveagent.gateway.v1.AgentEnvelope.error:type_name -> liveagent.gateway.v1.ErrorResponse - 9, // 89: liveagent.gateway.v1.UploadReadableFilesRequest.files:type_name -> liveagent.gateway.v1.UploadReadableFile - 8, // 90: liveagent.gateway.v1.UploadReadableFilesResponse.files:type_name -> liveagent.gateway.v1.ChatUploadedFile - 16, // 91: liveagent.gateway.v1.TunnelControlResponse.tunnels:type_name -> liveagent.gateway.v1.TunnelSummary - 16, // 92: liveagent.gateway.v1.TunnelControlResponse.tunnel:type_name -> liveagent.gateway.v1.TunnelSummary - 0, // 93: liveagent.gateway.v1.TunnelFrame.kind:type_name -> liveagent.gateway.v1.TunnelFrameKind - 17, // 94: liveagent.gateway.v1.TunnelFrame.headers:type_name -> liveagent.gateway.v1.TunnelHeader - 23, // 95: liveagent.gateway.v1.TerminalSession.ssh:type_name -> liveagent.gateway.v1.TerminalSshMetadata - 25, // 96: liveagent.gateway.v1.SftpResponse.entries:type_name -> liveagent.gateway.v1.SftpEntry - 25, // 97: liveagent.gateway.v1.SftpResponse.entry:type_name -> liveagent.gateway.v1.SftpEntry - 26, // 98: liveagent.gateway.v1.SftpResponse.transfer:type_name -> liveagent.gateway.v1.SftpTransfer - 26, // 99: liveagent.gateway.v1.SftpEvent.transfer:type_name -> liveagent.gateway.v1.SftpTransfer - 31, // 100: liveagent.gateway.v1.TerminalSshTabsSnapshot.tabs:type_name -> liveagent.gateway.v1.TerminalSshTab - 22, // 101: liveagent.gateway.v1.TerminalResponse.sessions:type_name -> liveagent.gateway.v1.TerminalSession - 22, // 102: liveagent.gateway.v1.TerminalResponse.session:type_name -> liveagent.gateway.v1.TerminalSession - 30, // 103: liveagent.gateway.v1.TerminalResponse.shell_options:type_name -> liveagent.gateway.v1.TerminalShellOption - 29, // 104: liveagent.gateway.v1.TerminalResponse.ssh_prompt:type_name -> liveagent.gateway.v1.TerminalSshPrompt - 32, // 105: liveagent.gateway.v1.TerminalResponse.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot - 22, // 106: liveagent.gateway.v1.TerminalEvent.session:type_name -> liveagent.gateway.v1.TerminalSession - 32, // 107: liveagent.gateway.v1.TerminalEvent.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot - 22, // 108: liveagent.gateway.v1.TerminalStreamFrame.session:type_name -> liveagent.gateway.v1.TerminalSession - 6, // 109: liveagent.gateway.v1.ChatRequest.selected_model:type_name -> liveagent.gateway.v1.ChatSelectedModel - 8, // 110: liveagent.gateway.v1.ChatRequest.uploaded_files:type_name -> liveagent.gateway.v1.ChatUploadedFile - 7, // 111: liveagent.gateway.v1.ChatRequest.runtime_controls:type_name -> liveagent.gateway.v1.ChatRuntimeControls - 38, // 112: liveagent.gateway.v1.ChatCommandRequest.request:type_name -> liveagent.gateway.v1.ChatRequest - 39, // 113: liveagent.gateway.v1.ChatCommandRequest.base_message_ref:type_name -> liveagent.gateway.v1.ChatMessageRef - 40, // 114: liveagent.gateway.v1.ChatCommandRequest.cancel:type_name -> liveagent.gateway.v1.CancelChatRequest - 1, // 115: liveagent.gateway.v1.ChatEvent.type:type_name -> liveagent.gateway.v1.ChatEvent.ChatEventType - 49, // 116: liveagent.gateway.v1.HistoryListResponse.conversations:type_name -> liveagent.gateway.v1.ConversationSummary - 49, // 117: liveagent.gateway.v1.HistoryGetResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 39, // 118: liveagent.gateway.v1.HistoryPrefixRequest.base_message_ref:type_name -> liveagent.gateway.v1.ChatMessageRef - 49, // 119: liveagent.gateway.v1.HistoryPrefixResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 49, // 120: liveagent.gateway.v1.HistoryRenameResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 49, // 121: liveagent.gateway.v1.HistoryPinResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 58, // 122: liveagent.gateway.v1.HistoryShareGetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus - 58, // 123: liveagent.gateway.v1.HistoryShareSetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus - 49, // 124: liveagent.gateway.v1.HistoryShareResolveResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 66, // 125: liveagent.gateway.v1.HistoryWorkdirsResponse.workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirSummary - 49, // 126: liveagent.gateway.v1.HistorySyncEvent.conversation:type_name -> liveagent.gateway.v1.ConversationSummary - 89, // 127: liveagent.gateway.v1.FileMentionListResponse.entries:type_name -> liveagent.gateway.v1.FileMentionEntry - 91, // 128: liveagent.gateway.v1.FsRootsResponse.roots:type_name -> liveagent.gateway.v1.FsRoot - 95, // 129: liveagent.gateway.v1.FsListDirsResponse.entries:type_name -> liveagent.gateway.v1.FsDirEntry - 100, // 130: liveagent.gateway.v1.FsListResponse.entries:type_name -> liveagent.gateway.v1.FsListEntry - 5, // 131: liveagent.gateway.v1.AgentGateway.AgentConnect:input_type -> liveagent.gateway.v1.AgentEnvelope - 35, // 132: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:input_type -> liveagent.gateway.v1.TerminalStreamFrame - 2, // 133: liveagent.gateway.v1.AgentGateway.Authenticate:input_type -> liveagent.gateway.v1.AuthRequest - 4, // 134: liveagent.gateway.v1.AgentGateway.AgentConnect:output_type -> liveagent.gateway.v1.GatewayEnvelope - 35, // 135: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:output_type -> liveagent.gateway.v1.TerminalStreamFrame - 3, // 136: liveagent.gateway.v1.AgentGateway.Authenticate:output_type -> liveagent.gateway.v1.AuthResponse - 134, // [134:137] is the sub-list for method output_type - 131, // [131:134] is the sub-list for method input_type - 131, // [131:131] is the sub-list for extension type_name - 131, // [131:131] is the sub-list for extension extendee - 0, // [0:131] is the sub-list for field type_name + 80, // 40: liveagent.gateway.v1.GatewayEnvelope.settings_reset_ssh_known_host:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostRequest + 42, // 41: liveagent.gateway.v1.GatewayEnvelope.chat_queue:type_name -> liveagent.gateway.v1.ChatQueueRequest + 45, // 42: liveagent.gateway.v1.AgentEnvelope.chat_event:type_name -> liveagent.gateway.v1.ChatEvent + 49, // 43: liveagent.gateway.v1.AgentEnvelope.cron_manage_resp:type_name -> liveagent.gateway.v1.CronManageResponse + 51, // 44: liveagent.gateway.v1.AgentEnvelope.history_list_resp:type_name -> liveagent.gateway.v1.HistoryListResponse + 54, // 45: liveagent.gateway.v1.AgentEnvelope.history_get_resp:type_name -> liveagent.gateway.v1.HistoryGetResponse + 58, // 46: liveagent.gateway.v1.AgentEnvelope.history_rename_resp:type_name -> liveagent.gateway.v1.HistoryRenameResponse + 72, // 47: liveagent.gateway.v1.AgentEnvelope.history_delete_resp:type_name -> liveagent.gateway.v1.HistoryDeleteResponse + 73, // 48: liveagent.gateway.v1.AgentEnvelope.history_sync:type_name -> liveagent.gateway.v1.HistorySyncEvent + 56, // 49: liveagent.gateway.v1.AgentEnvelope.history_prefix_resp:type_name -> liveagent.gateway.v1.HistoryPrefixResponse + 60, // 50: liveagent.gateway.v1.AgentEnvelope.history_pin_resp:type_name -> liveagent.gateway.v1.HistoryPinResponse + 63, // 51: liveagent.gateway.v1.AgentEnvelope.history_share_get_resp:type_name -> liveagent.gateway.v1.HistoryShareGetResponse + 65, // 52: liveagent.gateway.v1.AgentEnvelope.history_share_set_resp:type_name -> liveagent.gateway.v1.HistoryShareSetResponse + 67, // 53: liveagent.gateway.v1.AgentEnvelope.history_share_resolve_resp:type_name -> liveagent.gateway.v1.HistoryShareResolveResponse + 70, // 54: liveagent.gateway.v1.AgentEnvelope.history_workdirs_resp:type_name -> liveagent.gateway.v1.HistoryWorkdirsResponse + 75, // 55: liveagent.gateway.v1.AgentEnvelope.provider_list_resp:type_name -> liveagent.gateway.v1.ProviderListResponse + 77, // 56: liveagent.gateway.v1.AgentEnvelope.settings_get_resp:type_name -> liveagent.gateway.v1.SettingsGetResponse + 79, // 57: liveagent.gateway.v1.AgentEnvelope.settings_update_resp:type_name -> liveagent.gateway.v1.SettingsUpdateResponse + 82, // 58: liveagent.gateway.v1.AgentEnvelope.settings_sync:type_name -> liveagent.gateway.v1.SettingsSyncEvent + 84, // 59: liveagent.gateway.v1.AgentEnvelope.skill_files_list_resp:type_name -> liveagent.gateway.v1.SkillFilesListResponse + 86, // 60: liveagent.gateway.v1.AgentEnvelope.skill_metadata_read_resp:type_name -> liveagent.gateway.v1.SkillMetadataReadResponse + 88, // 61: liveagent.gateway.v1.AgentEnvelope.skill_text_read_resp:type_name -> liveagent.gateway.v1.SkillTextReadResponse + 93, // 62: liveagent.gateway.v1.AgentEnvelope.file_mention_list_resp:type_name -> liveagent.gateway.v1.FileMentionListResponse + 11, // 63: liveagent.gateway.v1.AgentEnvelope.upload_readable_files_resp:type_name -> liveagent.gateway.v1.UploadReadableFilesResponse + 96, // 64: liveagent.gateway.v1.AgentEnvelope.fs_roots_resp:type_name -> liveagent.gateway.v1.FsRootsResponse + 118, // 65: liveagent.gateway.v1.AgentEnvelope.pong:type_name -> liveagent.gateway.v1.PongResponse + 99, // 66: liveagent.gateway.v1.AgentEnvelope.fs_list_dirs_resp:type_name -> liveagent.gateway.v1.FsListDirsResponse + 13, // 67: liveagent.gateway.v1.AgentEnvelope.uploaded_image_preview_resp:type_name -> liveagent.gateway.v1.UploadedImagePreviewResponse + 20, // 68: liveagent.gateway.v1.AgentEnvelope.memory_manage_resp:type_name -> liveagent.gateway.v1.MemoryManageResponse + 90, // 69: liveagent.gateway.v1.AgentEnvelope.skill_manage_resp:type_name -> liveagent.gateway.v1.SkillManageResponse + 101, // 70: liveagent.gateway.v1.AgentEnvelope.fs_create_project_folder_resp:type_name -> liveagent.gateway.v1.FsCreateProjectFolderResponse + 33, // 71: liveagent.gateway.v1.AgentEnvelope.terminal_response:type_name -> liveagent.gateway.v1.TerminalResponse + 34, // 72: liveagent.gateway.v1.AgentEnvelope.terminal_event:type_name -> liveagent.gateway.v1.TerminalEvent + 104, // 73: liveagent.gateway.v1.AgentEnvelope.fs_list_resp:type_name -> liveagent.gateway.v1.FsListResponse + 110, // 74: liveagent.gateway.v1.AgentEnvelope.fs_write_text_resp:type_name -> liveagent.gateway.v1.FsWriteTextResponse + 112, // 75: liveagent.gateway.v1.AgentEnvelope.fs_create_dir_resp:type_name -> liveagent.gateway.v1.FsCreateDirResponse + 114, // 76: liveagent.gateway.v1.AgentEnvelope.fs_rename_resp:type_name -> liveagent.gateway.v1.FsRenameResponse + 116, // 77: liveagent.gateway.v1.AgentEnvelope.fs_delete_resp:type_name -> liveagent.gateway.v1.FsDeleteResponse + 37, // 78: liveagent.gateway.v1.AgentEnvelope.git_response:type_name -> liveagent.gateway.v1.GitResponse + 106, // 79: liveagent.gateway.v1.AgentEnvelope.fs_read_editable_text_resp:type_name -> liveagent.gateway.v1.FsReadEditableTextResponse + 108, // 80: liveagent.gateway.v1.AgentEnvelope.fs_read_workspace_image_resp:type_name -> liveagent.gateway.v1.FsReadWorkspaceImageResponse + 27, // 81: liveagent.gateway.v1.AgentEnvelope.sftp_response:type_name -> liveagent.gateway.v1.SftpResponse + 28, // 82: liveagent.gateway.v1.AgentEnvelope.sftp_event:type_name -> liveagent.gateway.v1.SftpEvent + 43, // 83: liveagent.gateway.v1.AgentEnvelope.chat_queue_resp:type_name -> liveagent.gateway.v1.ChatQueueResponse + 44, // 84: liveagent.gateway.v1.AgentEnvelope.chat_queue_event:type_name -> liveagent.gateway.v1.ChatQueueEvent + 14, // 85: liveagent.gateway.v1.AgentEnvelope.tunnel_control:type_name -> liveagent.gateway.v1.TunnelControlRequest + 15, // 86: liveagent.gateway.v1.AgentEnvelope.tunnel_control_resp:type_name -> liveagent.gateway.v1.TunnelControlResponse + 18, // 87: liveagent.gateway.v1.AgentEnvelope.tunnel_frame:type_name -> liveagent.gateway.v1.TunnelFrame + 46, // 88: liveagent.gateway.v1.AgentEnvelope.chat_control:type_name -> liveagent.gateway.v1.ChatControlEvent + 47, // 89: liveagent.gateway.v1.AgentEnvelope.runtime_status:type_name -> liveagent.gateway.v1.RuntimeStatusEvent + 81, // 90: liveagent.gateway.v1.AgentEnvelope.settings_reset_ssh_known_host_resp:type_name -> liveagent.gateway.v1.SettingsResetSshKnownHostResponse + 119, // 91: liveagent.gateway.v1.AgentEnvelope.error:type_name -> liveagent.gateway.v1.ErrorResponse + 9, // 92: liveagent.gateway.v1.UploadReadableFilesRequest.files:type_name -> liveagent.gateway.v1.UploadReadableFile + 8, // 93: liveagent.gateway.v1.UploadReadableFilesResponse.files:type_name -> liveagent.gateway.v1.ChatUploadedFile + 16, // 94: liveagent.gateway.v1.TunnelControlResponse.tunnels:type_name -> liveagent.gateway.v1.TunnelSummary + 16, // 95: liveagent.gateway.v1.TunnelControlResponse.tunnel:type_name -> liveagent.gateway.v1.TunnelSummary + 0, // 96: liveagent.gateway.v1.TunnelFrame.kind:type_name -> liveagent.gateway.v1.TunnelFrameKind + 17, // 97: liveagent.gateway.v1.TunnelFrame.headers:type_name -> liveagent.gateway.v1.TunnelHeader + 23, // 98: liveagent.gateway.v1.TerminalSession.ssh:type_name -> liveagent.gateway.v1.TerminalSshMetadata + 25, // 99: liveagent.gateway.v1.SftpResponse.entries:type_name -> liveagent.gateway.v1.SftpEntry + 25, // 100: liveagent.gateway.v1.SftpResponse.entry:type_name -> liveagent.gateway.v1.SftpEntry + 26, // 101: liveagent.gateway.v1.SftpResponse.transfer:type_name -> liveagent.gateway.v1.SftpTransfer + 26, // 102: liveagent.gateway.v1.SftpEvent.transfer:type_name -> liveagent.gateway.v1.SftpTransfer + 31, // 103: liveagent.gateway.v1.TerminalSshTabsSnapshot.tabs:type_name -> liveagent.gateway.v1.TerminalSshTab + 22, // 104: liveagent.gateway.v1.TerminalResponse.sessions:type_name -> liveagent.gateway.v1.TerminalSession + 22, // 105: liveagent.gateway.v1.TerminalResponse.session:type_name -> liveagent.gateway.v1.TerminalSession + 30, // 106: liveagent.gateway.v1.TerminalResponse.shell_options:type_name -> liveagent.gateway.v1.TerminalShellOption + 29, // 107: liveagent.gateway.v1.TerminalResponse.ssh_prompt:type_name -> liveagent.gateway.v1.TerminalSshPrompt + 32, // 108: liveagent.gateway.v1.TerminalResponse.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot + 22, // 109: liveagent.gateway.v1.TerminalEvent.session:type_name -> liveagent.gateway.v1.TerminalSession + 32, // 110: liveagent.gateway.v1.TerminalEvent.ssh_tabs:type_name -> liveagent.gateway.v1.TerminalSshTabsSnapshot + 22, // 111: liveagent.gateway.v1.TerminalStreamFrame.session:type_name -> liveagent.gateway.v1.TerminalSession + 6, // 112: liveagent.gateway.v1.ChatRequest.selected_model:type_name -> liveagent.gateway.v1.ChatSelectedModel + 8, // 113: liveagent.gateway.v1.ChatRequest.uploaded_files:type_name -> liveagent.gateway.v1.ChatUploadedFile + 7, // 114: liveagent.gateway.v1.ChatRequest.runtime_controls:type_name -> liveagent.gateway.v1.ChatRuntimeControls + 38, // 115: liveagent.gateway.v1.ChatCommandRequest.request:type_name -> liveagent.gateway.v1.ChatRequest + 39, // 116: liveagent.gateway.v1.ChatCommandRequest.base_message_ref:type_name -> liveagent.gateway.v1.ChatMessageRef + 40, // 117: liveagent.gateway.v1.ChatCommandRequest.cancel:type_name -> liveagent.gateway.v1.CancelChatRequest + 1, // 118: liveagent.gateway.v1.ChatEvent.type:type_name -> liveagent.gateway.v1.ChatEvent.ChatEventType + 52, // 119: liveagent.gateway.v1.HistoryListResponse.conversations:type_name -> liveagent.gateway.v1.ConversationSummary + 52, // 120: liveagent.gateway.v1.HistoryGetResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 39, // 121: liveagent.gateway.v1.HistoryPrefixRequest.base_message_ref:type_name -> liveagent.gateway.v1.ChatMessageRef + 52, // 122: liveagent.gateway.v1.HistoryPrefixResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 52, // 123: liveagent.gateway.v1.HistoryRenameResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 52, // 124: liveagent.gateway.v1.HistoryPinResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 61, // 125: liveagent.gateway.v1.HistoryShareGetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus + 61, // 126: liveagent.gateway.v1.HistoryShareSetResponse.share:type_name -> liveagent.gateway.v1.HistoryShareStatus + 52, // 127: liveagent.gateway.v1.HistoryShareResolveResponse.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 69, // 128: liveagent.gateway.v1.HistoryWorkdirsResponse.workdirs:type_name -> liveagent.gateway.v1.HistoryWorkdirSummary + 52, // 129: liveagent.gateway.v1.HistorySyncEvent.conversation:type_name -> liveagent.gateway.v1.ConversationSummary + 92, // 130: liveagent.gateway.v1.FileMentionListResponse.entries:type_name -> liveagent.gateway.v1.FileMentionEntry + 94, // 131: liveagent.gateway.v1.FsRootsResponse.roots:type_name -> liveagent.gateway.v1.FsRoot + 98, // 132: liveagent.gateway.v1.FsListDirsResponse.entries:type_name -> liveagent.gateway.v1.FsDirEntry + 103, // 133: liveagent.gateway.v1.FsListResponse.entries:type_name -> liveagent.gateway.v1.FsListEntry + 5, // 134: liveagent.gateway.v1.AgentGateway.AgentConnect:input_type -> liveagent.gateway.v1.AgentEnvelope + 35, // 135: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:input_type -> liveagent.gateway.v1.TerminalStreamFrame + 2, // 136: liveagent.gateway.v1.AgentGateway.Authenticate:input_type -> liveagent.gateway.v1.AuthRequest + 4, // 137: liveagent.gateway.v1.AgentGateway.AgentConnect:output_type -> liveagent.gateway.v1.GatewayEnvelope + 35, // 138: liveagent.gateway.v1.AgentGateway.AgentTerminalConnect:output_type -> liveagent.gateway.v1.TerminalStreamFrame + 3, // 139: liveagent.gateway.v1.AgentGateway.Authenticate:output_type -> liveagent.gateway.v1.AuthResponse + 137, // [137:140] is the sub-list for method output_type + 134, // [134:137] is the sub-list for method input_type + 134, // [134:134] is the sub-list for extension type_name + 134, // [134:134] is the sub-list for extension extendee + 0, // [0:134] is the sub-list for field type_name } func init() { file_proto_v1_gateway_proto_init() } @@ -10285,6 +10623,7 @@ func file_proto_v1_gateway_proto_init() { (*GatewayEnvelope_TunnelControlResp)(nil), (*GatewayEnvelope_TunnelFrame)(nil), (*GatewayEnvelope_SettingsResetSshKnownHost)(nil), + (*GatewayEnvelope_ChatQueue)(nil), } file_proto_v1_gateway_proto_msgTypes[3].OneofWrappers = []any{ (*AgentEnvelope_ChatEvent)(nil), @@ -10328,6 +10667,8 @@ func file_proto_v1_gateway_proto_init() { (*AgentEnvelope_FsReadWorkspaceImageResp)(nil), (*AgentEnvelope_SftpResponse)(nil), (*AgentEnvelope_SftpEvent)(nil), + (*AgentEnvelope_ChatQueueResp)(nil), + (*AgentEnvelope_ChatQueueEvent)(nil), (*AgentEnvelope_TunnelControl)(nil), (*AgentEnvelope_TunnelControlResp)(nil), (*AgentEnvelope_TunnelFrame)(nil), @@ -10336,14 +10677,14 @@ func file_proto_v1_gateway_proto_init() { (*AgentEnvelope_SettingsResetSshKnownHostResp)(nil), (*AgentEnvelope_Error)(nil), } - file_proto_v1_gateway_proto_msgTypes[59].OneofWrappers = []any{} + file_proto_v1_gateway_proto_msgTypes[62].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_v1_gateway_proto_rawDesc), len(file_proto_v1_gateway_proto_rawDesc)), NumEnums: 2, - NumMessages: 115, + NumMessages: 118, NumExtensions: 0, NumServices: 1, }, diff --git a/crates/agent-gateway/internal/server/chat_commands.go b/crates/agent-gateway/internal/server/chat_commands.go index f9f17f50..e6a82200 100644 --- a/crates/agent-gateway/internal/server/chat_commands.go +++ b/crates/agent-gateway/internal/server/chat_commands.go @@ -63,6 +63,7 @@ func normalizeChatRequestBody(body *handler.ChatRequestBody) error { body.ClientRequestID = strings.TrimSpace(body.ClientRequestID) body.ExecutionMode = handler.NormalizeExecutionMode(body.ExecutionMode) body.Workdir = handler.NormalizeWorkdir(body.Workdir) + body.QueuePolicy = normalizeChatQueuePolicy(body.QueuePolicy) body.SelectedSystemTools = handler.NormalizeSelectedSystemTools(body.SelectedSystemTools) body.UploadedFiles = handler.NormalizeChatUploadedFiles(body.UploadedFiles) body.RuntimeControls = handler.NormalizeChatRuntimeControls(body.RuntimeControls) @@ -80,6 +81,15 @@ func normalizeChatRequestBody(body *handler.ChatRequestBody) error { return nil } +func normalizeChatQueuePolicy(value string) string { + switch strings.TrimSpace(value) { + case "append", "interrupt": + return strings.TrimSpace(value) + default: + return "auto" + } +} + func startAcceptedChatCommand( sm *session.Manager, requestID string, @@ -285,6 +295,7 @@ func buildProtoChatRequest(body handler.ChatRequestBody) *gatewayv1.ChatRequest Workdir: body.Workdir, SelectedSystemTools: body.SelectedSystemTools, UploadedFiles: handler.ToProtoChatUploadedFiles(body.UploadedFiles), + QueuePolicy: body.QueuePolicy, } } diff --git a/crates/agent-gateway/internal/server/chat_payloads.go b/crates/agent-gateway/internal/server/chat_payloads.go index 2d5ce385..e8d78a7f 100644 --- a/crates/agent-gateway/internal/server/chat_payloads.go +++ b/crates/agent-gateway/internal/server/chat_payloads.go @@ -97,6 +97,8 @@ func chatEventType(eventType gatewayv1.ChatEvent_ChatEventType) string { return "tool_status" case gatewayv1.ChatEvent_HOSTED_SEARCH: return "hosted_search" + case gatewayv1.ChatEvent_USER_MESSAGE: + return "user_message" default: return "message" } diff --git a/crates/agent-gateway/internal/server/http_chat.go b/crates/agent-gateway/internal/server/http_chat.go index 3907ab79..7e4aa899 100644 --- a/crates/agent-gateway/internal/server/http_chat.go +++ b/crates/agent-gateway/internal/server/http_chat.go @@ -133,17 +133,18 @@ func handleChatCancelCommandHTTP( return } if runID == "" { - if snapshot, ok := sm.ChatRunSnapshot("", conversationID); ok { + if snapshot, ok := sm.RunningChatRunSnapshot(conversationID); ok { runID = strings.TrimSpace(snapshot.RequestID) if conversationID == "" { conversationID = strings.TrimSpace(snapshot.ConversationID) } } } - requestID := runID - if requestID == "" { - requestID = "chat-cancel-" + uuid.NewString() + if runID == "" { + writeJSON(w, http.StatusAccepted, map[string]any{"accepted": true, "run_id": "", "conversation_id": conversationID}) + return } + requestID := runID timeout := 10 * time.Second if cfg != nil && cfg.WebSocketWriteTimeout > 0 { timeout = cfg.WebSocketWriteTimeout @@ -359,7 +360,7 @@ func cloudChatEventType(payload map[string]any) string { func isChatControlPayload(payload map[string]any) bool { eventType, _ := payload["type"].(string) switch strings.TrimSpace(eventType) { - case "accepted", "user_message", "rebased", "projection_updated", "delivered", "claimed", "starting", "started", "progress", "completed", "failed", "cancelled": + case "accepted", "user_message", "rebased", "projection_updated", "delivered", "claimed", "starting", "queued_in_gui", "started", "progress", "completed", "failed", "cancelled": return true default: return false diff --git a/crates/agent-gateway/internal/server/http_test.go b/crates/agent-gateway/internal/server/http_test.go index c2cf3c91..9a8a5a10 100644 --- a/crates/agent-gateway/internal/server/http_test.go +++ b/crates/agent-gateway/internal/server/http_test.go @@ -576,6 +576,11 @@ func TestChatCancelCommandFindsRunByConversation(t *testing.T) { if _, created, err := sm.StartPendingChatCommandRun("run-1", "conversation-1", "client-1"); err != nil || !created { t.Fatalf("StartPendingChatCommandRun created=%v err=%v", created, err) } + sm.MarkChatRunControl("run-1", "conversation-1", "started", "", "") + startedSnapshot, ok := sm.ChatRunSnapshot("run-1", "conversation-1") + if !ok { + t.Fatal("expected started run snapshot") + } handler := NewHTTPServer(&config.Config{ Token: "dev-token", @@ -615,7 +620,7 @@ func TestChatCancelCommandFindsRunByConversation(t *testing.T) { t.Fatalf("expected status %d, got %d body %s", http.StatusAccepted, rec.Code, rec.Body.String()) } - ch, _, cleanup, _, err := sm.SubscribeChatRun("run-1", "conversation-1", 0) + ch, _, cleanup, _, err := sm.SubscribeChatRun("run-1", "conversation-1", startedSnapshot.LatestSeq) if err != nil { t.Fatalf("SubscribeChatRun: %v", err) } @@ -630,6 +635,119 @@ func TestChatCancelCommandFindsRunByConversation(t *testing.T) { } } +func TestChatCancelByConversationIgnoresDesktopQueuedRun(t *testing.T) { + sm := session.NewManager() + sm.RecordAuthentication("desktop-agent", "0.9.0", "session-1") + agentSession := session.NewAgentSession(sm.LatestAuthSnapshot()) + sm.SetSession(agentSession) + if _, created, err := sm.StartPendingChatCommandRun("queued-run", "conversation-1", "client-queued"); err != nil || !created { + t.Fatalf("StartPendingChatCommandRun queued created=%v err=%v", created, err) + } + sm.MarkChatRunControl("queued-run", "conversation-1", "queued_in_gui", "", "") + + handler := NewHTTPServer(&config.Config{ + Token: "dev-token", + RequestTimeout: time.Second, + WebSocketWriteTimeout: time.Second, + }, sm) + req := httptest.NewRequest( + http.MethodPost, + "http://gateway.test/api/chat/commands", + strings.NewReader(`{"type":"chat.cancel","payload":{"conversation_id":"conversation-1"}}`), + ) + req.Header.Set("Authorization", "Bearer dev-token") + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-LiveAgent-CSRF", "1") + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + select { + case outbound := <-agentSession.Outbound(): + outbound.Ack(nil) + t.Fatalf("unexpected outbound cancel for desktop queued run: %#v", outbound) + default: + } + if rec.Code != http.StatusAccepted { + t.Fatalf("expected status %d, got %d body %s", http.StatusAccepted, rec.Code, rec.Body.String()) + } + var decoded map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil { + t.Fatalf("decode cancel response: %v", err) + } + if decoded["accepted"] != true || decoded["run_id"] != "" || decoded["conversation_id"] != "conversation-1" { + t.Fatalf("cancel response = %#v", decoded) + } + snapshot, ok := sm.ChatRunSnapshot("queued-run", "conversation-1") + if !ok || snapshot.State != session.ChatRunStateDesktopQueued { + t.Fatalf("queued snapshot = %#v ok=%v, want desktop queued", snapshot, ok) + } +} + +func TestChatCancelByConversationUsesDesktopQueuedRunAfterHistoryRunning(t *testing.T) { + sm := session.NewManager() + sm.RecordAuthentication("desktop-agent", "0.9.0", "session-1") + agentSession := session.NewAgentSession(sm.LatestAuthSnapshot()) + sm.SetSession(agentSession) + if _, created, err := sm.StartPendingChatCommandRun("queued-run", "conversation-1", "client-queued"); err != nil || !created { + t.Fatalf("StartPendingChatCommandRun queued created=%v err=%v", created, err) + } + sm.MarkChatRunControl("queued-run", "conversation-1", "queued_in_gui", "", "") + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "history-running-1", + Payload: &gatewayv1.AgentEnvelope_HistorySync{ + HistorySync: &gatewayv1.HistorySyncEvent{ + Kind: "running", + ConversationId: "conversation-1", + Conversation: &gatewayv1.ConversationSummary{ + Id: "conversation-1", + }, + }, + }, + }) + + handler := NewHTTPServer(&config.Config{ + Token: "dev-token", + RequestTimeout: time.Second, + WebSocketWriteTimeout: time.Second, + }, sm) + req := httptest.NewRequest( + http.MethodPost, + "http://gateway.test/api/chat/commands", + strings.NewReader(`{"type":"chat.cancel","payload":{"conversation_id":"conversation-1"}}`), + ) + req.Header.Set("Authorization", "Bearer dev-token") + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-LiveAgent-CSRF", "1") + rec := httptest.NewRecorder() + done := make(chan struct{}) + go func() { + handler.ServeHTTP(rec, req) + close(done) + }() + + select { + case outbound := <-agentSession.Outbound(): + outbound.Ack(nil) + if outbound.GetRequestId() != "queued-run" { + t.Fatalf("cancel request id = %q, want queued-run", outbound.GetRequestId()) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for cancel chat request") + } + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("timed out waiting for cancel response") + } + if rec.Code != http.StatusAccepted { + t.Fatalf("expected status %d, got %d body %s", http.StatusAccepted, rec.Code, rec.Body.String()) + } + snapshot, ok := sm.ChatRunSnapshot("queued-run", "conversation-1") + if !ok || snapshot.State != session.ChatRunStateCancelled { + t.Fatalf("queued snapshot = %#v ok=%v, want cancelled", snapshot, ok) + } +} + func TestChatEditResendRejectsNegativeBaseMessageRef(t *testing.T) { handler := NewHTTPServer(&config.Config{Token: "dev-token"}, session.NewManager()) diff --git a/crates/agent-gateway/internal/server/websocket.go b/crates/agent-gateway/internal/server/websocket.go index 02d49cec..6ef286b5 100644 --- a/crates/agent-gateway/internal/server/websocket.go +++ b/crates/agent-gateway/internal/server/websocket.go @@ -101,15 +101,17 @@ type websocketConnection struct { done chan struct{} authorized bool - historyEvents <-chan *gatewayv1.HistorySyncEvent - historyEventsCleanup func() - settingsEvents <-chan *gatewayv1.SettingsSyncEvent - settingsEventsCleanup func() - terminalEvents <-chan *gatewayv1.TerminalEvent - terminalEventsCleanup func() - sftpEvents <-chan *gatewayv1.SftpEvent - sftpEventsCleanup func() - heartbeatOnce sync.Once + historyEvents <-chan *gatewayv1.HistorySyncEvent + historyEventsCleanup func() + settingsEvents <-chan *gatewayv1.SettingsSyncEvent + settingsEventsCleanup func() + terminalEvents <-chan *gatewayv1.TerminalEvent + terminalEventsCleanup func() + sftpEvents <-chan *gatewayv1.SftpEvent + sftpEventsCleanup func() + chatQueueEvents <-chan *gatewayv1.ChatQueueEvent + chatQueueEventsCleanup func() + heartbeatOnce sync.Once terminalInterest *websocketTerminalInterestTracker } @@ -209,6 +211,10 @@ func (c *websocketConnection) close() { c.sftpEventsCleanup() c.sftpEventsCleanup = nil } + if c.chatQueueEventsCleanup != nil { + c.chatQueueEventsCleanup() + c.chatQueueEventsCleanup = nil + } _ = c.conn.Close() }) } @@ -232,6 +238,7 @@ func (c *websocketConnection) handleAuth(req websocketRequest) { c.startSettingsSyncForwarder() c.startTerminalEventForwarder() c.startSftpEventForwarder() + c.startChatQueueEventForwarder() c.startWebSocketHeartbeat() if err := c.writeResponse(req.ID, map[string]any{"ok": true}); err != nil { c.close() @@ -358,6 +365,33 @@ func (c *websocketConnection) startSftpEventForwarder() { }() } +func (c *websocketConnection) startChatQueueEventForwarder() { + if c.chatQueueEvents != nil || c.chatQueueEventsCleanup != nil { + return + } + + chatQueueEvents, cleanup := c.sm.SubscribeChatQueueEvents() + c.chatQueueEvents = chatQueueEvents + c.chatQueueEventsCleanup = cleanup + + go func() { + for { + select { + case <-c.done: + return + case event, ok := <-chatQueueEvents: + if !ok { + return + } + if err := c.writeChatQueueEvent(websocketChatQueueEventPayload(event)); err != nil { + c.close() + return + } + } + } + }() +} + func (c *websocketConnection) replayTerminalSessionSnapshot() { if !c.terminalFeaturesEnabled() { return @@ -513,6 +547,13 @@ func (c *websocketConnection) writeSftpEvent(payload any) error { }) } +func (c *websocketConnection) writeChatQueueEvent(payload any) error { + return c.writeEnvelope(websocketEnvelope{ + Type: "chat_queue.event", + Payload: payload, + }) +} + func (c *websocketConnection) writeEnvelope(envelope websocketEnvelope) error { return c.writer.write(envelope) } diff --git a/crates/agent-gateway/internal/server/websocket_chat_queue_handlers.go b/crates/agent-gateway/internal/server/websocket_chat_queue_handlers.go new file mode 100644 index 00000000..15c7c794 --- /dev/null +++ b/crates/agent-gateway/internal/server/websocket_chat_queue_handlers.go @@ -0,0 +1,74 @@ +package server + +import ( + "strings" + "time" + + gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" +) + +type websocketChatQueueRequestPayload struct { + ConversationID string `json:"conversation_id"` + ItemID string `json:"item_id"` + Direction string `json:"direction"` + Revision uint64 `json:"revision"` + DraftJSON string `json:"draft_json"` + UploadedFilesJSON string `json:"uploaded_files_json"` + RequestJSON string `json:"request_json"` +} + +func chatQueueActionFromRequestType(requestType string) string { + return strings.TrimPrefix(strings.TrimSpace(requestType), "chat_queue.") +} + +func (c *websocketConnection) handleChatQueueRequest(req websocketRequest) { + var body websocketChatQueueRequestPayload + if err := decodeWebSocketPayload(req.Payload, &body); err != nil { + _ = c.writeError(req.ID, "invalid "+req.Type+" payload") + return + } + if !c.sm.IsOnline() { + _ = c.writeError(req.ID, "agent offline") + return + } + + response, err := c.awaitAgentResponse(req.ID, &gatewayv1.GatewayEnvelope{ + RequestId: req.ID, + Timestamp: time.Now().Unix(), + Payload: &gatewayv1.GatewayEnvelope_ChatQueue{ + ChatQueue: &gatewayv1.ChatQueueRequest{ + Action: chatQueueActionFromRequestType(req.Type), + ConversationId: strings.TrimSpace(body.ConversationID), + ItemId: strings.TrimSpace(body.ItemID), + Direction: strings.TrimSpace(body.Direction), + Revision: body.Revision, + DraftJson: strings.TrimSpace(body.DraftJSON), + UploadedFilesJson: strings.TrimSpace(body.UploadedFilesJSON), + RequestJson: strings.TrimSpace(body.RequestJSON), + }, + }, + }) + if err != nil { + _ = c.writeError(req.ID, websocketErrorMessage(err)) + return + } + if errResp := response.GetError(); errResp != nil { + _ = c.writeError(req.ID, errResp.GetMessage()) + return + } + + resp := response.GetChatQueueResp() + if resp == nil { + _ = c.writeError(req.ID, "unexpected agent response") + return + } + + _ = c.writeResponse(req.ID, map[string]any{ + "accepted": resp.GetAccepted(), + "message": resp.GetMessage(), + "snapshot_json": resp.GetSnapshotJson(), + "item_json": resp.GetItemJson(), + "error_code": resp.GetErrorCode(), + "revision": resp.GetRevision(), + }) +} diff --git a/crates/agent-gateway/internal/server/websocket_payloads.go b/crates/agent-gateway/internal/server/websocket_payloads.go index 9d6c177f..76c67285 100644 --- a/crates/agent-gateway/internal/server/websocket_payloads.go +++ b/crates/agent-gateway/internal/server/websocket_payloads.go @@ -209,6 +209,14 @@ func websocketSettingsJSONPayload(raw string) (map[string]any, error) { return payload, nil } +func websocketChatQueueEventPayload(event *gatewayv1.ChatQueueEvent) map[string]any { + return map[string]any{ + "conversation_id": strings.TrimSpace(event.GetConversationId()), + "snapshot_json": strings.TrimSpace(event.GetSnapshotJson()), + "revision": event.GetRevision(), + } +} + func websocketTerminalSessionPayload(session *gatewayv1.TerminalSession) map[string]any { if session == nil { return nil diff --git a/crates/agent-gateway/internal/server/websocket_routes.go b/crates/agent-gateway/internal/server/websocket_routes.go index 84ae7e36..6d1ce677 100644 --- a/crates/agent-gateway/internal/server/websocket_routes.go +++ b/crates/agent-gateway/internal/server/websocket_routes.go @@ -85,4 +85,12 @@ var websocketRequestHandlers = map[string]websocketRequestHandler{ "git.push": (*websocketConnection).handleGitRequest, "cron.manage": (*websocketConnection).handleCronManage, "provider.models": (*websocketConnection).handleProviderModels, + "chat_queue.get": (*websocketConnection).handleChatQueueRequest, + "chat_queue.get_item": (*websocketConnection).handleChatQueueRequest, + "chat_queue.run_now": (*websocketConnection).handleChatQueueRequest, + "chat_queue.move": (*websocketConnection).handleChatQueueRequest, + "chat_queue.remove": (*websocketConnection).handleChatQueueRequest, + "chat_queue.edit_begin": (*websocketConnection).handleChatQueueRequest, + "chat_queue.edit_commit": (*websocketConnection).handleChatQueueRequest, + "chat_queue.edit_cancel": (*websocketConnection).handleChatQueueRequest, } diff --git a/crates/agent-gateway/internal/server/websocket_routes_test.go b/crates/agent-gateway/internal/server/websocket_routes_test.go index 0d589279..32cc57be 100644 --- a/crates/agent-gateway/internal/server/websocket_routes_test.go +++ b/crates/agent-gateway/internal/server/websocket_routes_test.go @@ -89,6 +89,14 @@ func TestWebsocketRequestHandlersCoverKnownProtocolTypes(t *testing.T) { "git.push", "cron.manage", "provider.models", + "chat_queue.get", + "chat_queue.get_item", + "chat_queue.run_now", + "chat_queue.move", + "chat_queue.remove", + "chat_queue.edit_begin", + "chat_queue.edit_commit", + "chat_queue.edit_cancel", } for _, requestType := range expectedTypes { diff --git a/crates/agent-gateway/internal/session/manager.go b/crates/agent-gateway/internal/session/manager.go index d01efc7d..2e1db0cf 100644 --- a/crates/agent-gateway/internal/session/manager.go +++ b/crates/agent-gateway/internal/session/manager.go @@ -97,14 +97,15 @@ type ActiveChatRunSummary struct { } const ( - ChatRunStateQueued = "queued" - ChatRunStateDelivered = "delivered" - ChatRunStateClaimed = "claimed" - ChatRunStateStarting = "starting" - ChatRunStateRunning = "running" - ChatRunStateCompleted = "completed" - ChatRunStateFailed = "failed" - ChatRunStateCancelled = "cancelled" + ChatRunStateQueued = "queued" + ChatRunStateDelivered = "delivered" + ChatRunStateClaimed = "claimed" + ChatRunStateStarting = "starting" + ChatRunStateDesktopQueued = "desktop_queued" + ChatRunStateRunning = "running" + ChatRunStateCompleted = "completed" + ChatRunStateFailed = "failed" + ChatRunStateCancelled = "cancelled" ) type chatRun struct { diff --git a/crates/agent-gateway/internal/session/manager_chat_queue.go b/crates/agent-gateway/internal/session/manager_chat_queue.go new file mode 100644 index 00000000..415aba59 --- /dev/null +++ b/crates/agent-gateway/internal/session/manager_chat_queue.go @@ -0,0 +1,47 @@ +package session + +import ( + "time" + + gatewayv1 "github.com/liveagent/agent-gateway/internal/proto/v1" +) + +func (m *Manager) SubscribeChatQueueEvents() (<-chan *gatewayv1.ChatQueueEvent, func()) { + ch := make(chan *gatewayv1.ChatQueueEvent, 64) + + m.syncHub.chatQueueMu.Lock() + subID := m.syncHub.nextChatQueueSubID + m.syncHub.nextChatQueueSubID += 1 + m.syncHub.chatQueueSubscribers[subID] = ch + m.syncHub.chatQueueMu.Unlock() + + cleanup := func() { + m.syncHub.chatQueueMu.Lock() + if _, ok := m.syncHub.chatQueueSubscribers[subID]; ok { + delete(m.syncHub.chatQueueSubscribers, subID) + } + m.syncHub.chatQueueMu.Unlock() + } + + return ch, cleanup +} + +func (m *Manager) broadcastChatQueueEvent(event *gatewayv1.ChatQueueEvent) { + if event == nil { + return + } + + m.syncHub.chatQueueMu.Lock() + subscribers := make([]chan *gatewayv1.ChatQueueEvent, 0, len(m.syncHub.chatQueueSubscribers)) + for _, ch := range m.syncHub.chatQueueSubscribers { + subscribers = append(subscribers, ch) + } + m.syncHub.chatQueueMu.Unlock() + + for _, ch := range subscribers { + select { + case ch <- event: + case <-time.After(50 * time.Millisecond): + } + } +} diff --git a/crates/agent-gateway/internal/session/manager_chat_runs.go b/crates/agent-gateway/internal/session/manager_chat_runs.go index 7b6fc353..e826e220 100644 --- a/crates/agent-gateway/internal/session/manager_chat_runs.go +++ b/crates/agent-gateway/internal/session/manager_chat_runs.go @@ -115,7 +115,9 @@ func (m *Manager) ensureConversationChatRun( run.workdir = workdir } run.conversationID = conversationID - m.chatStore.chatRunByConversation[conversationID] = requestID + if m.chatRunCanClaimConversationLocked(conversationID, requestID) { + m.chatStore.chatRunByConversation[conversationID] = requestID + } run.applyState(ChatRunStateRunning) run.updatedAt = now return run.snapshot(), created, nil @@ -274,7 +276,7 @@ func (m *Manager) startPendingChatCommandRun( } run.applyState(ChatRunStateQueued) m.chatStore.chatRuns[requestID] = run - if conversationID != "" { + if conversationID != "" && m.chatRunCanClaimConversationLocked(conversationID, requestID) { m.chatStore.chatRunByConversation[conversationID] = requestID } if clientRequestID != "" { @@ -301,6 +303,27 @@ func (m *Manager) latestConversationSeqLocked(conversationID string) int64 { return latestSeq } +func (m *Manager) chatRunCanClaimConversationLocked(conversationID string, requestID string) bool { + conversationID = strings.TrimSpace(conversationID) + requestID = strings.TrimSpace(requestID) + if conversationID == "" || requestID == "" { + return false + } + currentRequestID := strings.TrimSpace(m.chatStore.chatRunByConversation[conversationID]) + if currentRequestID == "" || currentRequestID == requestID { + return true + } + currentRun := m.chatStore.chatRuns[currentRequestID] + return currentRun == nil || currentRun.done +} + +func chatRunControlCanClaimConversation(controlType string, state string) bool { + if normalizeChatRunState(state) == ChatRunStateRunning { + return true + } + return strings.TrimSpace(controlType) == "started" +} + func (m *Manager) upsertChatRunSnapshotLocked( snapshot ChatRunSnapshot, sessionEpoch uint64, @@ -345,7 +368,7 @@ func (m *Manager) upsertChatRunSnapshotLocked( } } m.chatStore.chatRuns[requestID] = run - if run.conversationID != "" { + if run.conversationID != "" && m.chatRunCanClaimConversationLocked(run.conversationID, requestID) { m.chatStore.chatRunByConversation[run.conversationID] = requestID } if run.clientRequestID != "" { @@ -370,7 +393,9 @@ func (m *Manager) applyChatRunSnapshotLocked(run *chatRun, snapshot ChatRunSnaps } } run.conversationID = conversationID - m.chatStore.chatRunByConversation[conversationID] = requestID + if m.chatRunCanClaimConversationLocked(conversationID, requestID) { + m.chatStore.chatRunByConversation[conversationID] = requestID + } } if clientRequestID := strings.TrimSpace(snapshot.ClientRequestID); clientRequestID != "" { run.clientRequestID = clientRequestID @@ -473,7 +498,8 @@ func (m *Manager) ActiveChatRunSummaries() []ActiveChatRunSummary { if summaries[index].Workdir == "" { summaries[index].Workdir = summary.Workdir } - if summary.UpdatedAt > summaries[index].UpdatedAt { + currentOwner := strings.TrimSpace(m.chatStore.chatRunByConversation[conversationID]) + if shouldReplaceActiveChatRunSummary(summary, summaries[index], currentOwner) { summaries[index].RequestID = summary.RequestID summaries[index].FirstSeq = summary.FirstSeq summaries[index].LatestSeq = summary.LatestSeq @@ -520,6 +546,30 @@ func (m *Manager) ActiveChatRunSummaries() []ActiveChatRunSummary { return summaries } +func shouldReplaceActiveChatRunSummary(candidate ActiveChatRunSummary, current ActiveChatRunSummary, currentOwner string) bool { + candidatePriority := activeChatRunSummaryPriority(candidate, currentOwner) + currentPriority := activeChatRunSummaryPriority(current, currentOwner) + if candidatePriority != currentPriority { + return candidatePriority > currentPriority + } + return candidate.UpdatedAt > current.UpdatedAt +} + +func activeChatRunSummaryPriority(summary ActiveChatRunSummary, currentOwner string) int { + requestID := strings.TrimSpace(summary.RequestID) + priority := 0 + if requestID != "" { + priority += 1 + } + if requestID != "" && !strings.HasPrefix(requestID, "conversation-live-") { + priority += 2 + } + if currentOwner != "" && requestID == currentOwner { + priority += 4 + } + return priority +} + func (m *Manager) ActiveChatRunConversationIDs() []string { summaries := m.ActiveChatRunSummaries() ids := make([]string, 0, len(summaries)) @@ -632,6 +682,7 @@ func (m *Manager) FailUnstartedChatRun(requestID string, message string) bool { } state := normalizeChatRunState(run.state) return state != ChatRunStateQueued && + state != ChatRunStateDesktopQueued && state != ChatRunStateRunning && !isTerminalChatRunState(state) }, @@ -942,6 +993,48 @@ func (m *Manager) ChatRunSnapshot( return run.snapshot(), true } +func (m *Manager) RunningChatRunSnapshot(conversationID string) (ChatRunSnapshot, bool) { + conversationID = strings.TrimSpace(conversationID) + if conversationID == "" { + return ChatRunSnapshot{}, false + } + + m.chatStore.chatMu.Lock() + defer m.chatStore.chatMu.Unlock() + m.pruneExpiredChatRunsLocked(time.Now()) + + if requestID := strings.TrimSpace(m.chatStore.chatRunByConversation[conversationID]); requestID != "" { + if run := m.chatStore.chatRuns[requestID]; chatRunIsRunningForConversation(run, conversationID) { + return run.snapshot(), true + } + } + + var best *chatRun + var bestRequestID string + for requestID, run := range m.chatStore.chatRuns { + if !chatRunIsRunningForConversation(run, conversationID) { + continue + } + if best == nil || + run.updatedAt.After(best.updatedAt) || + (run.updatedAt.Equal(best.updatedAt) && strings.TrimSpace(requestID) > bestRequestID) { + best = run + bestRequestID = strings.TrimSpace(requestID) + } + } + if best == nil { + return ChatRunSnapshot{}, false + } + return best.snapshot(), true +} + +func chatRunIsRunningForConversation(run *chatRun, conversationID string) bool { + return run != nil && + !run.done && + strings.TrimSpace(run.conversationID) == conversationID && + normalizeChatRunState(run.state) == ChatRunStateRunning +} + func (m *Manager) MarkChatRunControl( requestID string, conversationID string, @@ -1016,7 +1109,9 @@ func (m *Manager) MarkChatRunPayloads( } } run.conversationID = conversationID - m.chatStore.chatRunByConversation[conversationID] = requestID + if m.chatRunCanClaimConversationLocked(conversationID, requestID) { + m.chatStore.chatRunByConversation[conversationID] = requestID + } } for _, payload := range payloads { broadcast := m.appendChatPayloadLocked(run, payload, now) @@ -1089,7 +1184,9 @@ func (m *Manager) broadcastChatEvent(requestID string, event *gatewayv1.ChatEven run.applyState(ChatRunStateQueued) m.chatStore.chatRuns[requestID] = run if conversationID != "" { - m.chatStore.chatRunByConversation[conversationID] = requestID + if m.chatRunCanClaimConversationLocked(conversationID, requestID) { + m.chatStore.chatRunByConversation[conversationID] = requestID + } } } if run != nil { @@ -1104,7 +1201,9 @@ func (m *Manager) broadcastChatEvent(requestID string, event *gatewayv1.ChatEven } } run.conversationID = conversationID - m.chatStore.chatRunByConversation[conversationID] = requestID + if m.chatRunCanClaimConversationLocked(conversationID, requestID) { + m.chatStore.chatRunByConversation[conversationID] = requestID + } if run.workdir == "" { if activeRun, ok := m.chatStore.historyActiveRuns[conversationID]; ok { run.workdir = strings.TrimSpace(activeRun.workdir) @@ -1201,7 +1300,9 @@ func (m *Manager) markChatRunStateSilent( } } run.conversationID = conversationID - m.chatStore.chatRunByConversation[conversationID] = requestID + if state == ChatRunStateRunning || m.chatRunCanClaimConversationLocked(conversationID, requestID) { + m.chatStore.chatRunByConversation[conversationID] = requestID + } } run.applyState(state) run.updatedAt = now @@ -1262,7 +1363,10 @@ func (m *Manager) markChatRunControl( } } run.conversationID = conversationID - m.chatStore.chatRunByConversation[conversationID] = requestID + if chatRunControlCanClaimConversation(controlType, state) || + m.chatRunCanClaimConversationLocked(conversationID, requestID) { + m.chatStore.chatRunByConversation[conversationID] = requestID + } } broadcast := m.appendChatControlLocked(run, controlType, errorCode, message, now) persist = chatRunEventAppendSnapshot(run, broadcast, now) @@ -1336,6 +1440,11 @@ func (m *Manager) dispatchFromAgent(expected *AgentSession, env *gatewayv1.Agent return } + if chatQueueEvent := env.GetChatQueueEvent(); chatQueueEvent != nil { + m.broadcastChatQueueEvent(chatQueueEvent) + return + } + if tunnelFrame := env.GetTunnelFrame(); tunnelFrame != nil { m.dispatchTunnelFrame(tunnelFrame) return @@ -1424,6 +1533,7 @@ func (r *chatRun) shouldPrune(now time.Time) bool { return true } return normalizeChatRunState(r.state) != ChatRunStateRunning && + normalizeChatRunState(r.state) != ChatRunStateDesktopQueued && chatRunUpdatedBefore(r.updatedAt, now, chatRunStartRetention) } @@ -1518,6 +1628,8 @@ func normalizeChatRunState(state string) string { return ChatRunStateClaimed case ChatRunStateStarting: return ChatRunStateStarting + case ChatRunStateDesktopQueued: + return ChatRunStateDesktopQueued case ChatRunStateRunning: return ChatRunStateRunning case ChatRunStateCompleted: @@ -1542,7 +1654,7 @@ func isTerminalChatRunState(state string) bool { func ChatRunStateIsActive(state string) bool { switch normalizeChatRunState(state) { - case ChatRunStateQueued, ChatRunStateDelivered, ChatRunStateClaimed, ChatRunStateStarting, ChatRunStateRunning: + case ChatRunStateQueued, ChatRunStateDelivered, ChatRunStateClaimed, ChatRunStateStarting, ChatRunStateDesktopQueued, ChatRunStateRunning: return true default: return false @@ -1559,6 +1671,8 @@ func chatRunStateForControlType(controlType string) string { return ChatRunStateClaimed case "starting": return ChatRunStateStarting + case "queued_in_gui": + return ChatRunStateDesktopQueued case "started": return ChatRunStateRunning case "completed": @@ -1582,6 +1696,8 @@ func chatControlTypeForState(state string) string { return "claimed" case ChatRunStateStarting: return "starting" + case ChatRunStateDesktopQueued: + return "queued_in_gui" case ChatRunStateRunning: return "started" case ChatRunStateCompleted: diff --git a/crates/agent-gateway/internal/session/manager_state.go b/crates/agent-gateway/internal/session/manager_state.go index a2bc7e54..6e4aa4a1 100644 --- a/crates/agent-gateway/internal/session/manager_state.go +++ b/crates/agent-gateway/internal/session/manager_state.go @@ -49,6 +49,10 @@ type syncHub struct { sftpMu sync.Mutex nextSftpSubID int sftpSubscribers map[int]chan *gatewayv1.SftpEvent + + chatQueueMu sync.Mutex + nextChatQueueSubID int + chatQueueSubscribers map[int]chan *gatewayv1.ChatQueueEvent } func newSyncHub() *syncHub { @@ -59,6 +63,7 @@ func newSyncHub() *syncHub { terminalSessions: make(map[string]*gatewayv1.TerminalSession), terminalStreamSubscribers: make(map[int]chan *gatewayv1.TerminalStreamFrame), sftpSubscribers: make(map[int]chan *gatewayv1.SftpEvent), + chatQueueSubscribers: make(map[int]chan *gatewayv1.ChatQueueEvent), } } diff --git a/crates/agent-gateway/internal/session/sqlite_chat_event_store.go b/crates/agent-gateway/internal/session/sqlite_chat_event_store.go index 90210b0f..72403e35 100644 --- a/crates/agent-gateway/internal/session/sqlite_chat_event_store.go +++ b/crates/agent-gateway/internal/session/sqlite_chat_event_store.go @@ -791,6 +791,8 @@ func storedChatEventType(eventType gatewayv1.ChatEvent_ChatEventType) string { return "tool_status" case gatewayv1.ChatEvent_HOSTED_SEARCH: return "hosted_search" + case gatewayv1.ChatEvent_USER_MESSAGE: + return "user_message" default: return "message" } diff --git a/crates/agent-gateway/proto/v1/gateway.proto b/crates/agent-gateway/proto/v1/gateway.proto index c63c1a1b..1e2934c1 100644 --- a/crates/agent-gateway/proto/v1/gateway.proto +++ b/crates/agent-gateway/proto/v1/gateway.proto @@ -68,6 +68,7 @@ message GatewayEnvelope { TunnelControlResponse tunnel_control_resp = 68; TunnelFrame tunnel_frame = 69; SettingsResetSshKnownHostRequest settings_reset_ssh_known_host = 72; + ChatQueueRequest chat_queue = 73; } } @@ -117,6 +118,8 @@ message AgentEnvelope { FsReadWorkspaceImageResponse fs_read_workspace_image_resp = 66; SftpResponse sftp_response = 73; SftpEvent sftp_event = 74; + ChatQueueResponse chat_queue_resp = 75; + ChatQueueEvent chat_queue_event = 76; TunnelControlRequest tunnel_control = 67; TunnelControlResponse tunnel_control_resp = 68; TunnelFrame tunnel_frame = 69; @@ -453,6 +456,7 @@ message ChatRequest { repeated ChatUploadedFile uploaded_files = 7; string client_request_id = 8; ChatRuntimeControls runtime_controls = 9; + string queue_policy = 10; } message ChatMessageRef { @@ -475,6 +479,32 @@ message ChatCommandRequest { CancelChatRequest cancel = 4; } +message ChatQueueRequest { + string action = 1; + string conversation_id = 2; + string item_id = 3; + string direction = 4; + uint64 revision = 5; + string draft_json = 6; + string uploaded_files_json = 7; + string request_json = 8; +} + +message ChatQueueResponse { + bool accepted = 1; + string message = 2; + string snapshot_json = 3; + string item_json = 4; + string error_code = 5; + uint64 revision = 6; +} + +message ChatQueueEvent { + string conversation_id = 1; + string snapshot_json = 2; + uint64 revision = 3; +} + message ChatEvent { ChatEventType type = 1; string conversation_id = 2; @@ -489,6 +519,7 @@ message ChatEvent { ERROR = 5; TOOL_STATUS = 6; HOSTED_SEARCH = 7; + USER_MESSAGE = 8; } } diff --git a/crates/agent-gateway/test/session/manager_test.go b/crates/agent-gateway/test/session/manager_test.go index de7de2bf..820a5e9f 100644 --- a/crates/agent-gateway/test/session/manager_test.go +++ b/crates/agent-gateway/test/session/manager_test.go @@ -1066,6 +1066,125 @@ func TestHistoryRunningCreatesAttachableConversationRun(t *testing.T) { } } +func TestHistoryRunningPromotesDesktopQueuedCommandRun(t *testing.T) { + t.Parallel() + + sm := newTestSessionManager() + sm.SetSession(session.NewAgentSession(sm.LatestAuthSnapshot())) + + if _, created, _, err := sm.StartAcceptedChatCommandRun("request-queued", "conversation-1", "client-1", "/workspace", []map[string]any{{ + "type": "user_message", + "message": "queued prompt", + }}); err != nil || !created { + t.Fatalf("StartAcceptedChatCommandRun queued created=%v err=%v", created, err) + } + dispatchChatControl(sm, "request-queued", "conversation-1", "queued_in_gui", session.ChatRunStateDesktopQueued) + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "history-sync-1", + Payload: &gatewayv1.AgentEnvelope_HistorySync{ + HistorySync: &gatewayv1.HistorySyncEvent{ + Kind: "running", + ConversationId: "conversation-1", + Conversation: &gatewayv1.ConversationSummary{ + Id: "conversation-1", + Cwd: "/workspace", + }, + }, + }, + }) + + queuedSnapshot, ok := sm.ChatRunSnapshot("request-queued", "conversation-1") + if !ok || queuedSnapshot.State != session.ChatRunStateRunning || queuedSnapshot.Workdir != "/workspace" { + t.Fatalf("queued snapshot = %#v, ok=%v; want running queued request with workdir", queuedSnapshot, ok) + } + + summaries := sm.ActiveChatRunSummaries() + if len(summaries) != 1 || + summaries[0].ConversationID != "conversation-1" || + summaries[0].RequestID != "request-queued" || + summaries[0].FirstSeq != 1 || + summaries[0].LatestSeq != 3 { + t.Fatalf("active summaries = %#v, want request-queued replay cursor", summaries) + } + + ch, done, cleanup, snapshot, err := sm.SubscribeChatRun("request-queued", "conversation-1", 0) + if err != nil { + t.Fatalf("SubscribeChatRun: %v", err) + } + defer cleanup() + assertDoneOpen(t, done) + if snapshot.RequestID != "request-queued" || snapshot.State != session.ChatRunStateRunning { + t.Fatalf("snapshot = %#v, want running request-queued", snapshot) + } + + got := make([]string, 0, 3) + for len(got) < 3 { + select { + case event := <-ch: + eventType := "" + if event.Control != nil { + eventType = event.Control.GetType() + } else if event.Payload != nil { + eventType, _ = event.Payload["type"].(string) + } + got = append(got, fmt.Sprintf("%s:%d:%s", event.RequestID, event.Seq, eventType)) + case <-time.After(time.Second): + t.Fatalf("timed out waiting for promoted queued replay, got %#v", got) + } + } + want := []string{ + "request-queued:1:accepted", + "request-queued:2:user_message", + "request-queued:3:queued_in_gui", + } + for index := range want { + if got[index] != want[index] { + t.Fatalf("promoted queued replay = %#v, want %#v", got, want) + } + } +} + +func TestStartedRunKeepsConversationOwnerWhenPreviousRunEmitsLateEvent(t *testing.T) { + t.Parallel() + + sm := newTestSessionManager() + sm.SetSession(session.NewAgentSession(sm.LatestAuthSnapshot())) + startRunningChatCommandRun(t, sm, "request-old", "conversation-1") + if _, created, err := sm.StartPendingChatCommandRun("request-new", "conversation-1", "client-new"); err != nil || !created { + t.Fatalf("StartPendingChatCommandRun new created=%v err=%v", created, err) + } + dispatchChatControl(sm, "request-new", "conversation-1", "started", session.ChatRunStateRunning) + + sm.DispatchFromAgent(&gatewayv1.AgentEnvelope{ + RequestId: "request-old", + Payload: &gatewayv1.AgentEnvelope_ChatEvent{ + ChatEvent: &gatewayv1.ChatEvent{ + Type: gatewayv1.ChatEvent_TOKEN, + ConversationId: "conversation-1", + Data: `{"text":"late old token"}`, + }, + }, + }) + + summaries := sm.ActiveChatRunSummaries() + if len(summaries) != 1 || + summaries[0].ConversationID != "conversation-1" || + summaries[0].RequestID != "request-new" { + t.Fatalf("active summaries = %#v, want request-new", summaries) + } + + _, done, cleanup, snapshot, err := sm.SubscribeChatRun("", "conversation-1", 0) + if err != nil { + t.Fatalf("SubscribeChatRun: %v", err) + } + defer cleanup() + assertDoneOpen(t, done) + if snapshot.RequestID != "request-new" { + t.Fatalf("conversation snapshot request id = %q, want request-new", snapshot.RequestID) + } +} + func TestCompletedHistoryUpsertDoesNotPreemptTerminalChatEvent(t *testing.T) { t.Parallel() diff --git a/crates/agent-gateway/test/webui/chat-turn-queue.test.mjs b/crates/agent-gateway/test/webui/chat-turn-queue.test.mjs index da612f6a..167f2112 100644 --- a/crates/agent-gateway/test/webui/chat-turn-queue.test.mjs +++ b/crates/agent-gateway/test/webui/chat-turn-queue.test.mjs @@ -107,6 +107,13 @@ test("gateway web queued chat turn preview keeps structured draft hints compact" preview: "large paste body", }, }, + { + type: "fileMention", + reference: { + path: "src/notes.txt", + kind: "file", + }, + }, { type: "skillMention", skill: { @@ -118,7 +125,7 @@ test("gateway web queued chat turn preview keeps structured draft hints compact" }, ]); - assert.equal(queue.buildQueuedChatTurnPreview(richDraft), "hello pasted.txt$reviewer"); + assert.equal(queue.buildQueuedChatTurnPreview(richDraft), "hello pasted.txtnotes.txt$reviewer"); assert.equal(queue.queuedChatTurnHasContent(richDraft, []), true); assert.equal(queue.queuedChatTurnHasContent(draft(""), [{ fileName: "a.txt" }]), true); }); diff --git a/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs b/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs index a2c38510..a819057f 100644 --- a/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs +++ b/crates/agent-gateway/test/webui/gateway-socket-client.test.mjs @@ -448,7 +448,7 @@ test("SharedWorker gateway client sends conversation cancel directly over HTTP", payload: { status: { online: true }, error: null }, }); - await client.cancelChat(" conversation-1 "); + await client.cancelChat(" conversation-1 ", " run-1 "); assert.equal(fetchCalls.length, 1); assert.equal(fetchCalls[0].url.toString(), "https://gateway.example/api/chat/commands"); assert.equal(fetchCalls[0].init.method, "POST"); @@ -457,6 +457,7 @@ test("SharedWorker gateway client sends conversation cancel directly over HTTP", assert.deepEqual(JSON.parse(fetchCalls[0].init.body), { type: "chat.cancel", payload: { + run_id: "run-1", conversation_id: "conversation-1", }, }); @@ -470,6 +471,52 @@ test("SharedWorker gateway client sends conversation cancel directly over HTTP", } }); +test("SharedWorker gateway client emits chat queue snapshots from worker events", async () => { + installBrowser(); + FakeSharedWorker.instances = []; + globalThis.SharedWorker = FakeSharedWorker; + const loader = createWebModuleLoader(); + const { getGatewayWebSocketClient, resetGatewayWebSocketClient } = loader.loadModule("src/lib/gatewaySocket.ts"); + resetGatewayWebSocketClient(); + + const client = getGatewayWebSocketClient(" token "); + assert.equal(FakeSharedWorker.instances.length, 1); + const port = FakeSharedWorker.instances[0].port; + const connect = port.messages.find((message) => message.type === "connect"); + assert.ok(connect); + port.emit({ + type: "ready", + connection_id: connect.connection_id, + payload: { status: { online: true }, error: null }, + }); + + const snapshots = []; + client.subscribeChatQueue((snapshot) => snapshots.push(snapshot)); + const snapshot = { + conversationId: "conversation-1", + revision: 7, + items: [ + { + id: "queue-1", + previewText: "next prompt", + fileCount: 0, + createdAt: 123, + source: "gui", + editable: true, + }, + ], + }; + port.emit({ + type: "event", + event_type: "chat_queue", + connection_id: connect.connection_id, + payload: snapshot, + }); + + assert.deepEqual(snapshots, [snapshot]); + resetGatewayWebSocketClient(); +}); + test("GatewayWebSocketClient streamChatEvents sends replay cursor", async () => { installBrowser(); const realFetch = globalThis.fetch; @@ -646,6 +693,121 @@ test("GatewayWebSocketClient streamChatEvents ignores stale terminal events from } }); +test("GatewayWebSocketClient streamChatEvents ignores stale non-terminal events from older runs", async () => { + installBrowser(); + const realFetch = globalThis.fetch; + const fetchCalls = []; + const encoder = new TextEncoder(); + globalThis.fetch = async (rawUrl, init = {}) => { + const url = new URL(String(rawUrl)); + fetchCalls.push({ url, init }); + if (url.pathname !== "/api/chat/events") { + return new Response(JSON.stringify({ error: `unexpected ${url.pathname}` }), { status: 404 }); + } + const body = new ReadableStream({ + start(controller) { + controller.enqueue( + encoder.encode( + 'id: 3\nevent: chat.event\ndata: {"run_id":"old-run","snapshot_run_id":"live-run","seq":3,"payload":{"type":"token","text":"stale","conversation_id":"conversation-1"}}\n\n' + + 'id: 4\nevent: chat.event\ndata: {"run_id":"live-run","snapshot_run_id":"live-run","seq":4,"payload":{"type":"token","text":"fresh","conversation_id":"conversation-1"}}\n\n' + + 'id: 5\nevent: chat.event\ndata: {"run_id":"live-run","snapshot_run_id":"live-run","seq":5,"payload":{"type":"done","conversation_id":"conversation-1"}}\n\n', + ), + ); + controller.close(); + }, + }); + return new Response(body, { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + }); + }; + + const loader = createWebModuleLoader(); + const { getGatewayWebSocketClient, resetGatewayWebSocketClient } = loader.loadModule("src/lib/gatewaySocket.ts"); + resetGatewayWebSocketClient(); + + try { + const client = getGatewayWebSocketClient(" token "); + const events = []; + for await (const event of client.streamChatEvents(" conversation-1 ", { + runId: "live-run", + afterSeq: 0, + })) { + events.push(event); + } + + assert.equal(fetchCalls.length, 1); + assert.equal(fetchCalls[0].url.searchParams.get("run_id"), "live-run"); + assert.deepEqual(events, [ + { type: "token", text: "fresh", conversation_id: "conversation-1", seq: 4 }, + { type: "done", conversation_id: "conversation-1", seq: 5 }, + ]); + assert.equal(FakeWebSocket.instances.length, 0); + } finally { + globalThis.fetch = realFetch; + resetGatewayWebSocketClient(); + } +}); + +test("GatewayWebSocketClient streamChatEvents isolates a run after interrupt cancellation", async () => { + installBrowser(); + const realFetch = globalThis.fetch; + const fetchCalls = []; + const encoder = new TextEncoder(); + globalThis.fetch = async (rawUrl, init = {}) => { + const url = new URL(String(rawUrl)); + fetchCalls.push({ url, init }); + if (url.pathname !== "/api/chat/events") { + return new Response(JSON.stringify({ error: `unexpected ${url.pathname}` }), { status: 404 }); + } + const body = new ReadableStream({ + start(controller) { + controller.enqueue( + encoder.encode( + 'id: 1\nevent: chat.control\ndata: {"run_id":"old-run","snapshot_run_id":"new-run","seq":1,"payload":{"type":"cancelled","state":"cancelled","conversation_id":"conversation-1","seq":1}}\n\n' + + 'id: 2\nevent: chat.event\ndata: {"run_id":"old-run","snapshot_run_id":"new-run","seq":2,"payload":{"type":"token","text":"stale tail","conversation_id":"conversation-1","seq":2}}\n\n' + + 'id: 1\nevent: chat.control\ndata: {"run_id":"new-run","snapshot_run_id":"new-run","seq":1,"payload":{"type":"started","state":"running","conversation_id":"conversation-1","seq":1}}\n\n' + + 'id: 2\nevent: chat.event\ndata: {"run_id":"new-run","snapshot_run_id":"new-run","seq":2,"payload":{"type":"token","text":"fresh","conversation_id":"conversation-1","seq":2}}\n\n' + + 'id: 3\nevent: chat.event\ndata: {"run_id":"new-run","snapshot_run_id":"new-run","seq":3,"payload":{"type":"done","conversation_id":"conversation-1","seq":3}}\n\n', + ), + ); + controller.close(); + }, + }); + return new Response(body, { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + }); + }; + + const loader = createWebModuleLoader(); + const { getGatewayWebSocketClient, resetGatewayWebSocketClient } = loader.loadModule("src/lib/gatewaySocket.ts"); + resetGatewayWebSocketClient(); + + try { + const client = getGatewayWebSocketClient(" token "); + const events = []; + for await (const event of client.streamChatEvents(" conversation-1 ", { + runId: "new-run", + afterSeq: 0, + })) { + events.push(event); + } + + assert.equal(fetchCalls.length, 1); + assert.equal(fetchCalls[0].url.searchParams.get("run_id"), "new-run"); + assert.deepEqual(events, [ + { type: "started", state: "running", conversation_id: "conversation-1", seq: 1 }, + { type: "token", text: "fresh", conversation_id: "conversation-1", seq: 2 }, + { type: "done", conversation_id: "conversation-1", seq: 3 }, + ]); + assert.equal(FakeWebSocket.instances.length, 0); + } finally { + globalThis.fetch = realFetch; + resetGatewayWebSocketClient(); + } +}); + test("SharedWorker gateway client forwards foreground wakeups to the worker", async () => { installBrowser(); FakeSharedWorker.instances = []; @@ -933,6 +1095,7 @@ test("SharedWorker gateway client posts chat commands directly over HTTP", async native_web_search_enabled: true, reasoning: "xhigh", }, + queue_policy: "auto", }, }); assert.equal(fetchCalls[1].url.pathname, "/api/chat/events"); @@ -992,6 +1155,10 @@ test("Gateway SharedWorker broadcasts events with each port connection id", asyn return () => {}; } + subscribeChatQueue() { + return () => {}; + } + dispose() {} } @@ -1106,6 +1273,10 @@ test("Gateway SharedWorker applies foreground wakeups to the managed socket clie return () => {}; } + subscribeChatQueue() { + return () => {}; + } + noteForegroundWakeup() { this.wakeups += 1; } @@ -1171,6 +1342,10 @@ test("Gateway SharedWorker terminal metadata reaches every page while output sta return () => {}; } + subscribeChatQueue() { + return () => {}; + } + async listTerminals(projectPathKey) { this.calls.push(["listTerminals", projectPathKey ?? ""]); return [ @@ -1357,6 +1532,10 @@ test("Gateway SharedWorker forwards terminal stream snapshot and output messages return () => {}; } + subscribeChatQueue() { + return () => {}; + } + dispose() {} } @@ -1506,6 +1685,10 @@ test("Gateway SharedWorker keeps one upstream terminal stream until every port d return () => {}; } + subscribeChatQueue() { + return () => {}; + } + dispose() {} } @@ -2100,6 +2283,10 @@ test("Gateway SharedWorker forwards history share requests", async () => { return () => {}; } + subscribeChatQueue() { + return () => {}; + } + getHistoryShare(conversationID) { this.calls.push(["getHistoryShare", conversationID]); return { @@ -2241,6 +2428,10 @@ test("Gateway SharedWorker forwards tunnel requests", async () => { return () => {}; } + subscribeChatQueue() { + return () => {}; + } + listTunnels() { this.calls.push(["listTunnels"]); return [ @@ -2744,6 +2935,7 @@ test("GatewayWebSocketClient commandChat posts commands and streams SSE events u native_web_search_enabled: true, reasoning: "xhigh", }, + queue_policy: "auto", }, }); diff --git a/crates/agent-gateway/test/webui/history-chat-ui.test.mjs b/crates/agent-gateway/test/webui/history-chat-ui.test.mjs index 853f7e28..64e71f70 100644 --- a/crates/agent-gateway/test/webui/history-chat-ui.test.mjs +++ b/crates/agent-gateway/test/webui/history-chat-ui.test.mjs @@ -386,7 +386,6 @@ test("normalizeRunningConversations preserves replay cursors and merges fallback run_id: " run-1 ", cwd: " /workspace ", first_seq: 42.9, - latest_seq: 99.8, run_epoch: 3.2, updated_at: 123, }, @@ -397,7 +396,6 @@ test("normalizeRunningConversations preserves replay cursors and merges fallback { conversation_id: "conversation-2", first_seq: 0, - latest_seq: -1, }, ], ["conversation-2", " conversation-3 "], @@ -408,7 +406,6 @@ test("normalizeRunningConversations preserves replay cursors and merges fallback run_id: "run-1", cwd: "/workspace", first_seq: 42, - latest_seq: 99, run_epoch: 3, updated_at: 123, }, @@ -417,7 +414,6 @@ test("normalizeRunningConversations preserves replay cursors and merges fallback run_id: undefined, cwd: undefined, first_seq: undefined, - latest_seq: undefined, run_epoch: undefined, updated_at: undefined, }, @@ -435,6 +431,10 @@ test("resolveRunningConversationStreamAfterSeq starts remote replay at current r assert.equal(historySync.resolveRunningConversationStreamAfterSeq(0), 0); assert.equal(historySync.resolveRunningConversationStreamAfterSeq(undefined), 0); assert.equal(historySync.resolveRunningConversationStreamAfterSeq("42"), 0); + assert.equal( + historySync.resolveRunningConversationStreamAfterSeq(42, { runId: "chat-command-1" }), + 0, + ); }); test("applyGatewayHistoryEvent can protect optimistic titles from summary broadcasts", () => { @@ -485,7 +485,14 @@ test("parseHistoryMessagesJson preserves upload display text and checkpoint meta sizeBytes: 42, }, ], - liveAgentHistoryRef: { segmentIndex: 1, messageIndex: 2 }, + liveAgentHistoryRef: { + segmentIndex: 1, + messageIndex: 2, + segmentId: "segment-1", + messageId: "message-2", + role: "user", + contentHash: "hash-2", + }, }, { role: "summary", @@ -506,7 +513,14 @@ test("parseHistoryMessagesJson preserves upload display text and checkpoint meta assert.equal(entries[0].kind, "user"); assert.equal(entries[0].text, "please inspect notes"); assert.equal(entries[0].attachments[0].relativePath, "uploads/notes.txt"); - assert.deepEqual(entries[0].messageRef, { segmentIndex: 1, messageIndex: 2 }); + assert.deepEqual(entries[0].messageRef, { + segmentIndex: 1, + messageIndex: 2, + segmentId: "segment-1", + messageId: "message-2", + role: "user", + contentHash: "hash-2", + }); assert.equal(entries[1].kind, "checkpoint"); assert.equal(entries[1].summaryId, "summary-1"); assert.equal(entries[1].coveredMessageCount, 8); @@ -1246,6 +1260,231 @@ test("createLiveConversationStreamStore ignores replayed events with the same se assert.equal(store.getSnapshot().entries[0].text, "fresh"); }); +test("createLiveConversationStreamStore does not render queue control events", () => { + globalThis.window = undefined; + globalThis.document = { visibilityState: "visible" }; + + const store = liveStore.createLiveConversationStreamStore(); + store.appendEvent({ + type: "accepted", + state: "queued", + conversation_id: "conversation-1", + seq: 1, + }); + store.appendEvent({ + type: "queued_in_gui", + state: "desktop_queued", + conversation_id: "conversation-1", + seq: 2, + }); + store.appendEvent({ + type: "started", + state: "running", + conversation_id: "conversation-1", + seq: 3, + }); + store.appendEvent({ + type: "token", + text: "visible", + round: 1, + conversation_id: "conversation-1", + seq: 4, + }); + + assert.equal(store.getSnapshot().entries.length, 1); + assert.equal(store.getSnapshot().entries[0].text, "visible"); +}); + +test("createLiveConversationStreamStore renders live user_message events", () => { + globalThis.window = undefined; + globalThis.document = { visibilityState: "visible" }; + + const store = liveStore.createLiveConversationStreamStore(); + store.appendEvent({ + type: "user_message", + message: "queued from gui", + uploaded_files: [ + { + relative_path: "notes.md", + absolute_path: "/workspace/notes.md", + file_name: "notes.md", + kind: "text", + size_bytes: 12, + }, + ], + conversation_id: "conversation-1", + seq: 1, + }); + store.appendEvent({ + type: "token", + text: "reply", + round: 1, + conversation_id: "conversation-1", + seq: 2, + }); + + const entries = store.getSnapshot().entries; + assert.equal(entries.length, 2); + assert.equal(entries[0].kind, "user"); + assert.equal(entries[0].text, "queued from gui"); + assert.equal(entries[0].attachments.length, 1); + assert.equal(entries[0].attachments[0].relativePath, "notes.md"); + assert.equal(entries[1].kind, "assistant"); + assert.equal(entries[1].text, "reply"); +}); + +function findTreeNode(node, predicate) { + if (Array.isArray(node)) { + for (const child of node) { + const match = findTreeNode(child, predicate); + if (match) { + return match; + } + } + return null; + } + if (node == null || typeof node !== "object") { + return null; + } + if (predicate(node)) { + return node; + } + const children = node.props?.children; + const childList = Array.isArray(children) ? children : [children]; + for (const child of childList) { + const match = findTreeNode(child, predicate); + if (match) { + return match; + } + } + return null; +} + +test("GatewayTranscript live renderer shows user bubbles before live assistant output", () => { + const fakeReact = { + createContext(defaultValue) { + return { defaultValue }; + }, + memo(component) { + return component; + }, + useCallback(callback) { + return callback; + }, + useContext(context) { + return context.defaultValue; + }, + useEffect() {}, + useLayoutEffect() {}, + useMemo(factory) { + return factory(); + }, + useRef(value) { + return { current: value }; + }, + useState(initialValue) { + const value = typeof initialValue === "function" ? initialValue() : initialValue; + return [value, () => {}]; + }, + useSyncExternalStore(_subscribe, getSnapshot) { + return getSnapshot(); + }, + }; + const transcriptLoader = createWebModuleLoader({ + mocks: { + react: fakeReact, + "@tanstack/react-virtual": { + useVirtualizer() { + return { + getTotalSize: () => 0, + getVirtualItems: () => [], + measureElement: () => {}, + }; + }, + }, + "@/components/Markdown": { + Markdown(props) { + return { type: "Markdown", props }; + }, + }, + "@/components/chat/ImagePreview": { + ImagePreview(props) { + return { type: "ImagePreview", props }; + }, + }, + "@/pages/chat/AssistantBubble": { + AssistantAvatar() { + return { type: "AssistantAvatar", props: {} }; + }, + AssistantBubble(props) { + return { type: "AssistantBubble", props }; + }, + CompactingText(props) { + return { type: "CompactingText", props }; + }, + VibingText(props) { + return { type: "VibingText", props }; + }, + }, + }, + }); + const transcriptLiveStore = transcriptLoader.loadModule("src/lib/liveConversationStreamStore.ts"); + const { GatewayTranscript } = transcriptLoader.loadModule("src/components/GatewayTranscript.tsx"); + + globalThis.window = undefined; + globalThis.document = { visibilityState: "visible" }; + + const store = transcriptLiveStore.createLiveConversationStreamStore(); + store.appendEvent( + { + type: "user_message", + message: "queued from gui", + conversation_id: "conversation-1", + seq: 1, + }, + { flush: true }, + ); + store.appendEvent( + { + type: "token", + text: "reply", + round: 1, + conversation_id: "conversation-1", + seq: 2, + }, + { flush: true }, + ); + + const transcriptTree = GatewayTranscript({ + conversationId: "conversation-1", + entries: [], + liveStore: store, + hasLiveStream: true, + isStreaming: true, + }); + const liveStateNode = findTreeNode( + transcriptTree, + (node) => typeof node.type === "function" && node.props?.liveSnapshot, + ); + + assert.ok(liveStateNode); + const liveTree = liveStateNode.type(liveStateNode.props); + assert.ok( + findTreeNode( + liveTree, + (node) => + typeof node.props?.className === "string" && + node.props.className.includes("gateway-transcript-row-user"), + ), + ); + assert.ok( + findTreeNode( + liveTree, + (node) => typeof node.type === "function" && node.props?.text === "queued from gui", + ), + ); +}); + test("mergeHistorySnapshotEntries appends remote user turn without dropping loaded history", () => { const existing = [ { id: "existing-user-1", kind: "user", text: "first", attachments: [] }, @@ -1382,6 +1621,46 @@ test("omitEquivalentTailEntries treats assistant metadata placement as the same ); }); +test("omitEquivalentTailEntries removes live user and assistant overlap", () => { + const existing = [ + { id: "history-user-1", kind: "user", text: "first", attachments: [] }, + { id: "history-assistant-1", kind: "assistant", text: "first answer", round: 1 }, + { id: "history-user-2", kind: "user", text: "queued from gui", attachments: [] }, + { id: "history-assistant-2", kind: "assistant", text: "reply", round: 1 }, + ]; + const liveEntries = [ + { id: "live-user-2", kind: "user", text: "queued from gui", attachments: [] }, + { id: "live-assistant-2", kind: "assistant", text: "reply", round: 1 }, + ]; + + const visibleHistory = liveCommit.omitEquivalentTailEntries(existing, liveEntries); + + assert.deepEqual( + visibleHistory.map((entry) => entry.text), + ["first", "first answer"], + ); +}); + +test("appendCommittedLiveEntries does not duplicate optimistic user message overlaps", () => { + const existing = [ + { id: "history-user-1", kind: "user", text: "first", attachments: [] }, + { id: "optimistic-user-2", kind: "user", text: "queued from gui", attachments: [] }, + ]; + const liveEntries = [ + { id: "live-user-2", kind: "user", text: "queued from gui", attachments: [] }, + { id: "live-assistant-2", kind: "assistant", text: "reply", round: 1 }, + ]; + + const merged = liveCommit.appendCommittedLiveEntries(existing, liveEntries); + + assert.deepEqual( + merged.map((entry) => entry.text), + ["first", "queued from gui", "reply"], + ); + assert.equal(merged[1].id, "optimistic-user-2"); + assert.equal(merged[2].kind, "assistant"); +}); + test("mergeHistorySnapshotEntries replaces stale local entries when an authoritative snapshot is shorter", () => { // Simulates: peer A edits the user prompt and resends, server-side history // is now just the new user turn. Without `isFullSnapshot`, the local two-turn diff --git a/crates/agent-gateway/web/src/app/GatewayApp.tsx b/crates/agent-gateway/web/src/app/GatewayApp.tsx index 2ce89b32..6dca1896 100644 --- a/crates/agent-gateway/web/src/app/GatewayApp.tsx +++ b/crates/agent-gateway/web/src/app/GatewayApp.tsx @@ -36,22 +36,7 @@ import { SkillsHubPage } from "@/pages/skills-hub/SkillsHubPage"; import { McpHubPage } from "@/pages/mcp-hub/McpHubPage"; import type { SectionId } from "@/pages/settings/types"; import { useChatSkills } from "@/pages/chat/useChatSkills"; -import { - appendQueuedChatTurn, - buildQueuedChatTurnPreview, - createQueuedChatTurn, - getQueuedConversationIds, - insertQueuedChatTurnAtSlot, - moveQueuedChatTurn, - promoteQueuedChatTurn, - queuedChatTurnHasContent, - removeQueuedChatTurn, - removeQueuedChatTurnsForConversation, - resolveQueuedChatTurnSlotIndex, - takeNextQueuedChatTurn, - type QueuedChatTurn, - type QueuedChatTurnEditSlot, -} from "@/pages/chat/queue/chatTurnQueue"; +import { queuedChatTurnHasContent } from "@/pages/chat/queue/chatTurnQueue"; import { mergeAlwaysEnabledSkillNames } from "@/lib/skills"; import { buildModelOptions, sortHistoryItems, VIBING_STATUS } from "@/lib/chat/chatPageHelpers"; import { SettingsPage } from "@/pages/SettingsPage"; @@ -85,6 +70,8 @@ import { terminalSessionBelongsToProject } from "@/lib/terminal/sessionStore"; import type { TerminalSession } from "@/lib/terminal/types"; import type { AgentStatus, + ChatQueueItemSummary, + ChatQueueSnapshot, ChatEvent, ConversationSummary, GatewayHistoryEvent, @@ -226,6 +213,23 @@ import { useGatewaySettingsSync } from "./hooks/useGatewaySettingsSync"; import { usePendingUploads } from "./hooks/usePendingUploads"; import { useProjectToolsRuntime } from "./hooks/useProjectToolsRuntime"; +function resolveRunningConversationRunKey( + runtime: Pick | null | undefined, +) { + const runId = runtime?.runId?.trim() ?? ""; + if (runId) { + return runId; + } + return typeof runtime?.runEpoch === "number" && Number.isFinite(runtime.runEpoch) + ? `epoch:${Math.floor(runtime.runEpoch)}` + : ""; +} + +function readGatewayChatEventRunId(event: ChatEvent) { + const runId = (event as ChatEvent & { __gatewayRunId?: unknown }).__gatewayRunId; + return typeof runId === "string" ? runId.trim() : ""; +} + export default function GatewayApp() { const historyShareToken = useMemo(() => parseHistoryShareToken(), []); const { @@ -259,7 +263,8 @@ export default function GatewayApp() { const [localRunningConversationIds, setLocalRunningConversationIds] = useState< ReadonlySet >(() => new Set()); - const [queuedChatTurns, setQueuedChatTurns] = useState([]); + const [queuedChatTurns, setQueuedChatTurns] = useState([]); + const [, setChatQueueRevision] = useState(0); const [remoteRunningConversationIds, setRemoteRunningConversationIds] = useState< ReadonlySet >(() => new Set()); @@ -371,18 +376,10 @@ export default function GatewayApp() { const chatErrorRef = useRef(chatError); const chatToolStatusRef = useRef(chatToolStatus); const chatToolStatusIsCompactionRef = useRef(chatToolStatusIsCompaction); - const queuedChatTurnsRef = useRef([]); - const queuedChatProcessingConversationIdsRef = useRef(new Set()); - const queuedChatTurnEditSlotRef = useRef< - | (QueuedChatTurnEditSlot & { - originalId: string; - createdAt: number; - workdir: string; - runtimeControls: ChatRuntimeControls; - }) - | null - >(null); - const previousRunningConversationIdsRef = useRef>(new Set()); + const queuedChatTurnsRef = useRef([]); + const chatQueueConversationIdRef = useRef(""); + const chatQueueRevisionRef = useRef(0); + const queuedChatEditSessionRef = useRef<{ itemId: string; revision: number } | null>(null); const selectedHistoryRef = useRef(selectedHistory); const selectedHistoryEntriesRef = useRef(selectedHistoryEntries); const historyItemsRef = useRef(historyItems); @@ -407,6 +404,7 @@ export default function GatewayApp() { const displayedConversationWorkdirRef = useRef(""); const conversationAbortControllersRef = useRef>(new Map()); const conversationEventStreamControllersRef = useRef>(new Map()); + const conversationEventStreamRunIdsRef = useRef>(new Map()); const completedLiveStreamConversationAtRef = useRef>(new Map()); const completedLiveStreamCleanupTimersRef = useRef>(new Map()); const pendingHistoryRefreshAfterLiveCompletionRef = useRef>(new Set()); @@ -474,15 +472,26 @@ export default function GatewayApp() { setChatError, }); - const setQueuedChatTurnsState = useCallback( - (updater: (current: QueuedChatTurn[]) => QueuedChatTurn[]) => { - const next = updater(queuedChatTurnsRef.current).slice(); - queuedChatTurnsRef.current = next; - setQueuedChatTurns(next); - return next; - }, - [], - ); + const applyChatQueueSnapshot = useCallback((snapshot: ChatQueueSnapshot | null | undefined) => { + if (!snapshot) return; + const visibleConversationId = resolveVisibleConversationId( + selectedHistoryIdRef.current, + conversationIdRef.current, + ); + if (snapshot.conversationId !== visibleConversationId) { + return; + } + const revision = Number(snapshot.revision ?? 0); + const isSameQueueConversation = snapshot.conversationId === chatQueueConversationIdRef.current; + if (isSameQueueConversation && revision < chatQueueRevisionRef.current) { + return; + } + chatQueueConversationIdRef.current = snapshot.conversationId; + chatQueueRevisionRef.current = revision; + queuedChatTurnsRef.current = snapshot.items.slice(); + setChatQueueRevision(revision); + setQueuedChatTurns(snapshot.items.slice()); + }, []); const recordProjectActivity = useCallback( (workdir?: string | null, updatedAt?: number | null) => { @@ -508,6 +517,13 @@ export default function GatewayApp() { [], ); + useEffect(() => { + if (!api) return; + return api.subscribeChatQueue((snapshot) => { + applyChatQueueSnapshot(snapshot); + }); + }, [api, applyChatQueueSnapshot]); + useEffect(() => { const historyActivity = buildWorkspaceProjectActivityUpdatedAts(historyWorkdirs); if (historyActivity.size === 0) { @@ -1904,7 +1920,6 @@ export default function GatewayApp() { runId?: string; workdir?: string; firstSeq?: number; - latestSeq?: number; runEpoch?: number; updatedAt?: number; }, @@ -1924,12 +1939,6 @@ export default function GatewayApp() { runtime.firstSeq > 0 ? Math.floor(runtime.firstSeq) : existingRuntime?.firstSeq; - const runtimeLatestSeq = - typeof runtime?.latestSeq === "number" && - Number.isFinite(runtime.latestSeq) && - runtime.latestSeq > 0 - ? Math.floor(runtime.latestSeq) - : existingRuntime?.latestSeq; const runtimeRunEpoch = typeof runtime?.runEpoch === "number" && Number.isFinite(runtime.runEpoch) && @@ -1942,6 +1951,32 @@ export default function GatewayApp() { runtime.updatedAt > 0 ? runtime.updatedAt : existingRuntime?.updatedAt || Date.now(); + const nextRunKey = isRunning + ? resolveRunningConversationRunKey({ + runId: runtimeRunId || undefined, + runEpoch: runtimeRunEpoch, + }) + : ""; + const previousRunKey = + resolveRunningConversationRunKey(existingRuntime) || + (conversationEventStreamRunIdsRef.current.get(conversationIdValue) ?? ""); + if (isRunning && nextRunKey && previousRunKey && previousRunKey !== nextRunKey) { + const existingController = + conversationEventStreamControllersRef.current.get(conversationIdValue); + if (existingController) { + conversationEventStreamControllersRef.current.delete(conversationIdValue); + conversationEventStreamRunIdsRef.current.delete(conversationIdValue); + existingController.abort(); + } + const completedTimeoutId = + completedLiveStreamCleanupTimersRef.current.get(conversationIdValue); + if (completedTimeoutId !== undefined) { + window.clearTimeout(completedTimeoutId); + completedLiveStreamCleanupTimersRef.current.delete(conversationIdValue); + } + completedLiveStreamConversationAtRef.current.delete(conversationIdValue); + clearConversationLiveStream(conversationIdValue); + } recordProjectActivity(runtimeWorkdir, runtimeUpdatedAt); if (!isRunning && !hasConversation && !existingRuntime) { return; @@ -1957,43 +1992,41 @@ export default function GatewayApp() { setRemoteRunningConversationIds(next); } - setRemoteRunningConversationRuntime((currentRuntime) => { - const existing = currentRuntime.get(conversationIdValue); - if (!isRunning) { - if (!existing) { - return currentRuntime; - } - const nextRuntime = new Map(currentRuntime); - nextRuntime.delete(conversationIdValue); - remoteRunningConversationRuntimeRef.current = nextRuntime; - return nextRuntime; - } - - const nextRuntimeEntry: RunningConversationRuntime = { - runId: runtimeRunId || undefined, - workdir: runtimeWorkdir || undefined, - firstSeq: runtimeFirstSeq, - latestSeq: runtimeLatestSeq, - runEpoch: runtimeRunEpoch, - updatedAt: Math.max(existing?.updatedAt ?? 0, runtimeUpdatedAt), - }; - if ( - existing?.runId === nextRuntimeEntry.runId && - existing?.workdir === nextRuntimeEntry.workdir && - existing?.firstSeq === nextRuntimeEntry.firstSeq && - existing?.latestSeq === nextRuntimeEntry.latestSeq && - existing?.runEpoch === nextRuntimeEntry.runEpoch && - existing?.updatedAt === nextRuntimeEntry.updatedAt - ) { - return currentRuntime; + const currentRuntime = remoteRunningConversationRuntimeRef.current; + const existing = currentRuntime.get(conversationIdValue); + if (!isRunning) { + if (!existing) { + return; } const nextRuntime = new Map(currentRuntime); - nextRuntime.set(conversationIdValue, nextRuntimeEntry); + nextRuntime.delete(conversationIdValue); remoteRunningConversationRuntimeRef.current = nextRuntime; - return nextRuntime; - }); + setRemoteRunningConversationRuntime(nextRuntime); + return; + } + + const nextRuntimeEntry: RunningConversationRuntime = { + runId: runtimeRunId || undefined, + workdir: runtimeWorkdir || undefined, + firstSeq: runtimeFirstSeq, + runEpoch: runtimeRunEpoch, + updatedAt: Math.max(existing?.updatedAt ?? 0, runtimeUpdatedAt), + }; + if ( + existing?.runId === nextRuntimeEntry.runId && + existing?.workdir === nextRuntimeEntry.workdir && + existing?.firstSeq === nextRuntimeEntry.firstSeq && + existing?.runEpoch === nextRuntimeEntry.runEpoch && + existing?.updatedAt === nextRuntimeEntry.updatedAt + ) { + return; + } + const nextRuntime = new Map(currentRuntime); + nextRuntime.set(conversationIdValue, nextRuntimeEntry); + remoteRunningConversationRuntimeRef.current = nextRuntime; + setRemoteRunningConversationRuntime(nextRuntime); }, - [recordProjectActivity], + [clearConversationLiveStream, recordProjectActivity], ); const isConversationEventStreamSubscribed = useCallback((targetConversationId: string) => { @@ -2014,12 +2047,14 @@ export default function GatewayApp() { return; } conversationEventStreamControllersRef.current.delete(conversationIdValue); + conversationEventStreamRunIdsRef.current.delete(conversationIdValue); controller.abort(); }, []); const stopAllConversationEventStreamSubscriptions = useCallback(() => { const controllers = [...conversationEventStreamControllersRef.current.values()]; conversationEventStreamControllersRef.current.clear(); + conversationEventStreamRunIdsRef.current.clear(); for (const controller of controllers) { controller.abort(); } @@ -2083,7 +2118,10 @@ export default function GatewayApp() { ); const clearConversationStreamingState = useCallback( - (targetConversationId: string) => { + ( + targetConversationId: string, + options?: { remoteRunId?: string; preserveRemoteRunOnMismatch?: boolean }, + ) => { const conversationIdValue = targetConversationId.trim(); if (!conversationIdValue) { return; @@ -2093,7 +2131,20 @@ export default function GatewayApp() { .get(conversationIdValue) ?.setToolStatus(null, false, { flush: true }); setLiveConversationStreamStatus(conversationIdValue, null); - setRemoteConversationRunningState(conversationIdValue, false); + const expectedRemoteRunKey = resolveRunningConversationRunKey({ + runId: options?.remoteRunId, + }); + const currentRemoteRunKey = resolveRunningConversationRunKey( + remoteRunningConversationRuntimeRef.current.get(conversationIdValue), + ); + const shouldClearRemoteRunning = + !options?.preserveRemoteRunOnMismatch || + !expectedRemoteRunKey || + !currentRemoteRunKey || + expectedRemoteRunKey === currentRemoteRunKey; + if (shouldClearRemoteRunning) { + setRemoteConversationRunningState(conversationIdValue, false); + } setConversationAbortController(conversationIdValue, null); setConversationRunningState(conversationIdValue, false); updateConversationRuntimeEntry(conversationIdValue, (current) => { @@ -2217,9 +2268,6 @@ export default function GatewayApp() { if (isLocalDraftConversationId(conversationIdValue)) { return; } - if (conversationEventStreamControllersRef.current.has(conversationIdValue)) { - return; - } if (!remoteRunningConversationIdsRef.current.has(conversationIdValue)) { return; } @@ -2236,18 +2284,36 @@ export default function GatewayApp() { return; } + const runtime = remoteRunningConversationRuntimeRef.current.get(conversationIdValue); + const nextRunKey = resolveRunningConversationRunKey(runtime); + const existingController = + conversationEventStreamControllersRef.current.get(conversationIdValue); + if (existingController) { + const existingRunKey = + conversationEventStreamRunIdsRef.current.get(conversationIdValue) ?? ""; + if (!nextRunKey || existingRunKey === nextRunKey) { + return; + } + conversationEventStreamControllersRef.current.delete(conversationIdValue); + conversationEventStreamRunIdsRef.current.delete(conversationIdValue); + existingController.abort(); + } + const controller = new AbortController(); conversationEventStreamControllersRef.current.set(conversationIdValue, controller); + conversationEventStreamRunIdsRef.current.set(conversationIdValue, nextRunKey); clearCompletedLiveStreamMarker(conversationIdValue); clearConversationLiveStream(conversationIdValue); - void refreshVisibleConversationHistorySnapshot(conversationIdValue, currentApi); const liveStore = getConversationLiveStreamStore(conversationIdValue); if (!liveStore) { conversationEventStreamControllersRef.current.delete(conversationIdValue); + conversationEventStreamRunIdsRef.current.delete(conversationIdValue); return; } - const runtime = remoteRunningConversationRuntimeRef.current.get(conversationIdValue); - const streamAfterSeq = resolveRunningConversationStreamAfterSeq(runtime?.firstSeq); + void refreshVisibleConversationHistorySnapshot(conversationIdValue, currentApi); + const streamAfterSeq = resolveRunningConversationStreamAfterSeq(runtime?.firstSeq, { + runId: runtime?.runId, + }); void (async () => { let terminalEventSeen = false; @@ -2323,6 +2389,7 @@ export default function GatewayApp() { conversationEventStreamControllersRef.current.get(conversationIdValue) === controller ) { conversationEventStreamControllersRef.current.delete(conversationIdValue); + conversationEventStreamRunIdsRef.current.delete(conversationIdValue); } if (!terminalEventSeen && controller.signal.aborted) { liveStore.flush(); @@ -2380,11 +2447,31 @@ export default function GatewayApp() { } if (event.kind === "running" || event.kind === "idle") { + const eventRunKey = + event.kind === "running" + ? resolveRunningConversationRunKey({ + runId: event.run_id, + runEpoch: event.run_epoch, + }) + : ""; + if (event.kind === "idle" && !eventRunKey) { + const activeRunKey = + conversationEventStreamRunIdsRef.current.get(targetConversationId) ?? + resolveRunningConversationRunKey( + remoteRunningConversationRuntimeRef.current.get(targetConversationId), + ); + if ( + activeRunKey && + conversationEventStreamControllersRef.current.has(targetConversationId) + ) { + void refreshHistoryWorkdirs(api); + return; + } + } setRemoteConversationRunningState(targetConversationId, event.kind === "running", { runId: event.run_id, workdir: event.conversation?.cwd, firstSeq: event.first_seq, - latestSeq: event.latest_seq, runEpoch: event.run_epoch, updatedAt: event.updated_at ?? event.conversation?.updated_at, }); @@ -2780,7 +2867,6 @@ export default function GatewayApp() { runId: runningConversation.run_id, workdir: runningConversation.cwd, firstSeq: runningConversation.first_seq, - latestSeq: runningConversation.latest_seq, runEpoch: runningConversation.run_epoch, updatedAt: runningConversation.updated_at, }); @@ -3016,7 +3102,6 @@ export default function GatewayApp() { runId: runningConversation.run_id, workdir: runningConversation.cwd, firstSeq: runningConversation.first_seq, - latestSeq: runningConversation.latest_seq, runEpoch: runningConversation.run_epoch, updatedAt: runningConversation.updated_at, }); @@ -3516,6 +3601,9 @@ export default function GatewayApp() { ) { stickTranscriptToBottom(); } + const optimisticUserEntryId = + options?.optimisticUserEntryId?.trim() || `user-${crypto.randomUUID()}`; + const shouldAppendOptimisticUserEntry = options?.skipOptimisticUserEntry !== true; updateConversationRuntimeEntry(activeConversationId, (current) => ({ ...current, error: null, @@ -3523,15 +3611,17 @@ export default function GatewayApp() { toolStatusIsCompaction: false, isSending: true, workdir: effectiveWorkdir || undefined, - messages: [ - ...current.messages, - { - id: `user-${crypto.randomUUID()}`, - kind: "user", - text: message, - attachments: uploadedFiles, - }, - ], + messages: shouldAppendOptimisticUserEntry + ? [ + ...current.messages, + { + id: optimisticUserEntryId, + kind: "user", + text: message, + attachments: uploadedFiles, + }, + ] + : current.messages, })); if (startedAsDraftConversation) { optimisticTitleConversationIdsRef.current.add(activeConversationId); @@ -3556,6 +3646,11 @@ export default function GatewayApp() { let terminalEventSeen = false; let runActive = false; let runtimeStarted = false; + let activeGatewayRunId = ""; + const preserveRemoteRunCleanupOptions = () => ({ + remoteRunId: activeGatewayRunId, + preserveRemoteRunOnMismatch: true, + }); let runtimeStartingStatusTimer: number | null = null; const clearRuntimeStartingStatusTimer = () => { if (runtimeStartingStatusTimer === null) { @@ -3672,8 +3767,13 @@ export default function GatewayApp() { clientRequestId, runtimeControls, baseMessageRef: options?.editMessageRef, + queuePolicy: options?.queuePolicy ?? "auto", }); for await (const event of chatStream) { + const eventRunId = readGatewayChatEventRunId(event); + if (eventRunId) { + activeGatewayRunId = eventRunId; + } if (event.conversation_id && event.conversation_id !== "") { const nextConversationId = event.conversation_id.trim(); if (nextConversationId !== activeConversationId) { @@ -3717,10 +3817,43 @@ export default function GatewayApp() { } } if (isChatControlEvent(event)) { + if (event.type === "queued_in_gui" || event.state === "desktop_queued") { + terminalEventSeen = true; + clearRuntimeStartingStatusTimer(); + clearConversationStreamingState( + activeConversationId, + preserveRemoteRunCleanupOptions(), + ); + updateConversationRuntimeEntry(activeConversationId, (current) => ({ + ...current, + messages: current.messages.filter((entry) => entry.id !== optimisticUserEntryId), + error: null, + toolStatus: null, + toolStatusIsCompaction: false, + isSending: false, + })); + const queueConversationId = event.conversation_id?.trim() || activeConversationId; + void api + .chatQueueGet(queueConversationId) + .then((response) => applyChatQueueSnapshot(response.snapshot)) + .catch(() => undefined); + return; + } + if (event.type === "user_message") { + markRunPreparing(); + getConversationLiveStreamStore(activeConversationId)?.appendEvent(event, { + flush: true, + }); + markLiveConversationStreamActive(activeConversationId); + continue; + } if (isTerminalChatControlEvent(event)) { terminalEventSeen = true; clearRuntimeStartingStatusTimer(); - clearConversationStreamingState(activeConversationId); + clearConversationStreamingState( + activeConversationId, + preserveRemoteRunCleanupOptions(), + ); if (event.type === "failed" || event.state === "failed") { getConversationLiveStreamStore(activeConversationId)?.appendEvent(event, { flush: true, @@ -3769,7 +3902,10 @@ export default function GatewayApp() { if (terminalEvent) { terminalEventSeen = true; clearRuntimeStartingStatusTimer(); - clearConversationStreamingState(activeConversationId); + clearConversationStreamingState( + activeConversationId, + preserveRemoteRunCleanupOptions(), + ); markCompletedLiveStream(activeConversationId); commitTerminalConversationLiveStream(activeConversationId); refreshHistoryAfterCompletedLiveStream(activeConversationId, api); @@ -3802,7 +3938,10 @@ export default function GatewayApp() { } finally { clearRuntimeStartingStatusTimer(); chatStartInFlightRef.current = false; - clearConversationStreamingState(activeConversationId); + clearConversationStreamingState(activeConversationId, preserveRemoteRunCleanupOptions()); + if (remoteRunningConversationIdsRef.current.has(activeConversationId)) { + subscribeVisibleConversationEventStream(activeConversationId, api); + } if (status?.online && !terminalEventSeen) { await reloadHistory(api, { preferredConversationId: activeConversationId, @@ -3834,6 +3973,10 @@ export default function GatewayApp() { return; } const controller = getConversationAbortController(activeConversationId); + const cancelRunId = + conversationEventStreamRunIdsRef.current.get(activeConversationId) ?? + remoteRunningConversationRuntimeRef.current.get(activeConversationId)?.runId ?? + ""; const isVisibleConversation = resolveVisibleConversationId(selectedHistoryIdRef.current, conversationIdRef.current) === activeConversationId; @@ -3843,7 +3986,7 @@ export default function GatewayApp() { api && activeConversationId !== "" && !isLocalDraftConversationId(activeConversationId) - ? api.cancelChat(activeConversationId).catch((error) => { + ? api.cancelChat(activeConversationId, cancelRunId).catch((error) => { if (!isAbortError(error)) { updateConversationRuntimeEntry(activeConversationId, (current) => ({ ...current, @@ -3879,17 +4022,6 @@ export default function GatewayApp() { } } - function isConversationRunningForQueue(conversationId: string) { - const key = conversationId.trim(); - return Boolean( - key && - (localRunningConversationIdsRef.current.has(key) || - remoteRunningConversationIdsRef.current.has(key) || - chatStartLocksRef.current.has(key) || - getConversationAbortController(key) !== null), - ); - } - async function materializeComposerDraftForSend( draft: MentionComposerDraft, files: PendingUploadedFile[], @@ -3933,201 +4065,225 @@ export default function GatewayApp() { clearCachedComposerDraft(key); } - function enqueueCurrentComposerTurn(position: "end" | "front" | "edit") { + async function submitCurrentComposerToGuiQueue(queuePolicy: "append" | "interrupt") { const conversationIdValue = getDisplayedConversationId(); const draft = composerRef.current?.getDraft() ?? null; const uploadedFiles = pendingUploadedFiles.slice(); - if (!conversationIdValue || !queuedChatTurnHasContent(draft, uploadedFiles)) { + let clearedComposer = false; + if (!api || !conversationIdValue || !queuedChatTurnHasContent(draft, uploadedFiles)) { return false; } const cachedRuntime = conversationRuntimeCacheRef.current.get(conversationIdValue); - const editSlot = - position === "edit" && - queuedChatTurnEditSlotRef.current?.conversationId === conversationIdValue - ? queuedChatTurnEditSlotRef.current - : null; const workdirForTurn = ( - editSlot?.workdir ?? cachedRuntime?.workdir ?? displayedConversationWorkdirRef.current ?? activeWorkspaceProjectPath ?? settings.system.workdir ).trim(); - const queuedTurn = createQueuedChatTurn({ - id: editSlot?.originalId, - conversationId: conversationIdValue, - draft, - uploadedFiles, - workdir: workdirForTurn, - runtimeControls: editSlot?.runtimeControls ?? chatRuntimeControlsForCurrentProvider, - createdAt: editSlot?.createdAt, - }); + try { + const materialized = await materializeComposerDraftForSend(draft, uploadedFiles, workdirForTurn); + if (!materialized.text && materialized.uploadedFiles.length === 0) { + return false; + } + clearCurrentComposerDraftForQueuedTurn(conversationIdValue); + clearedComposer = true; + const gatewaySelectedModel = buildGatewaySelectedModel(settings.selectedModel, activeProviders); + const gatewaySystemSettings = buildGatewaySystemSettings(settings, workdirForTurn); + const stream = api.commandChat({ + type: "chat.submit", + message: materialized.text, + conversationId: isLocalDraftConversationId(conversationIdValue) + ? undefined + : conversationIdValue, + selectedModel: gatewaySelectedModel, + systemSettings: gatewaySystemSettings, + uploadedFiles: materialized.uploadedFiles, + runtimeControls: chatRuntimeControlsForCurrentProvider, + queuePolicy, + }); + for await (const event of stream) { + if (event.type === "queued_in_gui" || ("state" in event && event.state === "desktop_queued")) { + const queueConversationId = event.conversation_id?.trim() || conversationIdValue; + void api + .chatQueueGet(queueConversationId) + .then((response) => applyChatQueueSnapshot(response.snapshot)) + .catch(() => undefined); + return true; + } + if (event.type === "failed" || event.type === "error") { + throw new Error("message" in event && typeof event.message === "string" ? event.message : "queue request failed"); + } + if (event.type === "started" || ("state" in event && event.state === "running")) { + return true; + } + } + return true; + } catch (error) { + if (clearedComposer && getDisplayedConversationId() === conversationIdValue) { + if (!composerRef.current?.hasContent()) { + composerRef.current?.setDraft(draft); + } + if ((pendingUploadsByConversationRef.current.get(conversationIdValue) ?? []).length === 0) { + setPendingUploadsForConversation(conversationIdValue, uploadedFiles); + } + } + updateConversationRuntimeEntry(conversationIdValue, (current) => ({ + ...current, + error: asErrorMessage(error, "queued chat request failed"), + })); + return false; + } + } - setQueuedChatTurnsState((current) => { - if (editSlot) { - return insertQueuedChatTurnAtSlot(current, queuedTurn, editSlot); + async function commitQueuedChatEdit() { + const session = queuedChatEditSessionRef.current; + const conversationIdValue = getDisplayedConversationId(); + if (!session || !api || !conversationIdValue) return false; + const draft = composerRef.current?.getDraft() ?? null; + const uploadedFiles = pendingUploadedFiles.slice(); + if (!queuedChatTurnHasContent(draft, uploadedFiles)) { + return false; + } + try { + const response = await api.chatQueueEditCommit({ + conversationId: conversationIdValue, + itemId: session.itemId, + revision: session.revision, + draftJson: JSON.stringify(draft), + uploadedFilesJson: JSON.stringify(uploadedFiles), + }); + if (!response.accepted) { + updateConversationRuntimeEntry(conversationIdValue, (current) => ({ + ...current, + error: response.message || "queued edit failed", + })); + return false; } - return position === "front" - ? promoteQueuedChatTurn(appendQueuedChatTurn(current, queuedTurn), queuedTurn.id) - : appendQueuedChatTurn(current, queuedTurn); - }); - if (editSlot) { - queuedChatTurnEditSlotRef.current = null; + queuedChatEditSessionRef.current = null; + composerRef.current?.clear(); + setPendingUploadsForConversation(conversationIdValue, []); + clearCachedComposerDraft(conversationIdValue); + applyChatQueueSnapshot(response.snapshot); + return true; + } catch (error) { + updateConversationRuntimeEntry(conversationIdValue, (current) => ({ + ...current, + error: asErrorMessage(error, "queued edit failed"), + })); + return false; } - clearCurrentComposerDraftForQueuedTurn(conversationIdValue); - return true; } - function isQueuedChatTurnEditBlockingProcessing(conversationId: string) { - const slot = queuedChatTurnEditSlotRef.current; + function reportChatQueueActionError( + conversationId: string, + error: unknown, + fallback: string, + ) { const key = conversationId.trim(); - if (!slot || slot.conversationId !== key) return false; - const queue = queuedChatTurnsRef.current; - const firstQueuedIndex = queue.findIndex((item) => item.conversationId === slot.conversationId); - if (firstQueuedIndex < 0) return false; - return resolveQueuedChatTurnSlotIndex(queue, slot) <= firstQueuedIndex; + if (!key) return; + updateConversationRuntimeEntry(key, (current) => ({ + ...current, + error: asErrorMessage(error, fallback), + })); } - function requestQueuedChatTurnProcessing(conversationId: string) { - const targetConversationId = conversationId.trim(); - if (!api || !targetConversationId) return; - if (queuedChatProcessingConversationIdsRef.current.has(targetConversationId)) return; - if (isConversationRunningForQueue(targetConversationId)) return; - if (isQueuedChatTurnEditBlockingProcessing(targetConversationId)) return; - if (!queuedChatTurnsRef.current.some((item) => item.conversationId === targetConversationId)) { - return; - } - - queuedChatProcessingConversationIdsRef.current.add(targetConversationId); - let inFlightQueuedTurn: QueuedChatTurn | null = null; - void Promise.resolve() - .then(async () => { - if (isConversationRunningForQueue(targetConversationId)) return false; - const taken = takeNextQueuedChatTurn(queuedChatTurnsRef.current, targetConversationId); - if (!taken.item) return false; - const queuedTurn = taken.item; - inFlightQueuedTurn = queuedTurn; - queuedChatTurnsRef.current = taken.queue; - setQueuedChatTurns(taken.queue); - const materialized = await materializeComposerDraftForSend( - queuedTurn.draft, - queuedTurn.uploadedFiles, - queuedTurn.workdir, - ); - if (!materialized.text && materialized.uploadedFiles.length === 0) { - return true; - } - await sendChat(materialized.text, { - conversationId: targetConversationId, - uploadedFiles: materialized.uploadedFiles, - runtimeControls: queuedTurn.runtimeControls, - workdir: queuedTurn.workdir, - }); - inFlightQueuedTurn = null; - return true; - }) - .then((accepted) => { - queuedChatProcessingConversationIdsRef.current.delete(targetConversationId); - if ( - accepted && - !isConversationRunningForQueue(targetConversationId) && - queuedChatTurnsRef.current.some((item) => item.conversationId === targetConversationId) - ) { - requestQueuedChatTurnProcessing(targetConversationId); + function runQueuedTurnNow(id: string) { + const conversationIdValue = getDisplayedConversationId(); + if (!api || !conversationIdValue) return; + void api + .chatQueueRunNow(conversationIdValue, id) + .then((response) => { + applyChatQueueSnapshot(response.snapshot); + for (const delayMs of [250, 1000]) { + window.setTimeout(() => { + void api + .chatQueueGet(conversationIdValue) + .then((nextResponse) => applyChatQueueSnapshot(nextResponse.snapshot)) + .catch(() => undefined); + }, delayMs); } }) .catch((error) => { - const failedQueuedTurn = inFlightQueuedTurn; - if (failedQueuedTurn) { - setQueuedChatTurnsState((current) => - promoteQueuedChatTurn( - appendQueuedChatTurn(current, failedQueuedTurn), - failedQueuedTurn.id, - ), - ); - } - const message = asErrorMessage(error, "queued chat request failed"); - updateConversationRuntimeEntry(targetConversationId, (current) => ({ - ...current, - error: message, - })); - queuedChatProcessingConversationIdsRef.current.delete(targetConversationId); + reportChatQueueActionError(conversationIdValue, error, "queued chat run failed"); }); } - function enqueueCurrentComposerTurnAndMaybeInterrupt() { + function moveQueuedTurnUp(id: string) { const conversationIdValue = getDisplayedConversationId(); - if (!enqueueCurrentComposerTurn("front")) return; - if (isConversationRunningForQueue(conversationIdValue)) { - void cancelChat(conversationIdValue); - return; - } - requestQueuedChatTurnProcessing(conversationIdValue); - } - - function runQueuedTurnNow(id: string) { - const queuedTurn = queuedChatTurnsRef.current.find((item) => item.id === id.trim()); - if (!queuedTurn) return; - setQueuedChatTurnsState((current) => promoteQueuedChatTurn(current, queuedTurn.id)); - if (isConversationRunningForQueue(queuedTurn.conversationId)) { - void cancelChat(queuedTurn.conversationId); - return; - } - requestQueuedChatTurnProcessing(queuedTurn.conversationId); - } - - function moveQueuedTurn(id: string, direction: "up" | "down") { - setQueuedChatTurnsState((current) => moveQueuedChatTurn(current, id, direction)); + if (!api || !conversationIdValue) return; + void api + .chatQueueMove(conversationIdValue, id, "up") + .then((response) => { + applyChatQueueSnapshot(response.snapshot); + }) + .catch((error) => { + reportChatQueueActionError(conversationIdValue, error, "queued chat move failed"); + }); } function editQueuedTurn(id: string) { - const key = id.trim(); - const queuedTurnIndex = queuedChatTurnsRef.current.findIndex((item) => item.id === key); - const queuedTurn = queuedTurnIndex >= 0 ? queuedChatTurnsRef.current[queuedTurnIndex] : null; - if (!queuedTurn) return; - const targetConversationId = queuedTurn.conversationId.trim(); - if (!targetConversationId || getDisplayedConversationId() !== targetConversationId) { - return; - } - - const currentDraft = composerRef.current?.getDraft() ?? null; - const currentUploads = pendingUploadedFiles.slice(); - if (queuedChatTurnHasContent(currentDraft, currentUploads)) { - enqueueCurrentComposerTurn(queuedChatTurnEditSlotRef.current ? "edit" : "end"); - } + const conversationIdValue = getDisplayedConversationId(); + if (!api || !conversationIdValue) return; + void (async () => { + if (queuedChatEditSessionRef.current) { + const committed = await commitQueuedChatEdit(); + if (!committed) return; + } else { + const currentDraft = composerRef.current?.getDraft() ?? null; + const currentUploads = pendingUploadedFiles.slice(); + if (queuedChatTurnHasContent(currentDraft, currentUploads)) { + const queued = await submitCurrentComposerToGuiQueue("append"); + if (!queued) return; + } + } - const sameConversationQueue = queuedChatTurnsRef.current.filter( - (item) => item.conversationId === targetConversationId, - ); - const sameConversationIndex = sameConversationQueue.findIndex((item) => item.id === key); - const previousId = - sameConversationIndex > 0 - ? (sameConversationQueue[sameConversationIndex - 1]?.id ?? null) - : null; - const nextId = - sameConversationIndex >= 0 - ? (sameConversationQueue[sameConversationIndex + 1]?.id ?? null) - : null; - queuedChatTurnEditSlotRef.current = { - conversationId: targetConversationId, - previousId, - nextId, - index: sameConversationIndex >= 0 ? sameConversationIndex : undefined, - originalId: queuedTurn.id, - createdAt: queuedTurn.createdAt, - workdir: queuedTurn.workdir, - runtimeControls: { ...queuedTurn.runtimeControls }, - }; - setQueuedChatTurnsState((current) => removeQueuedChatTurn(current, key)); - composerRef.current?.setDraft(queuedTurn.draft); - setPendingUploadsForConversation(targetConversationId, queuedTurn.uploadedFiles.slice()); - clearCachedComposerDraft(targetConversationId); - window.requestAnimationFrame(() => composerRef.current?.focus()); + const response = await api.chatQueueEditBegin(conversationIdValue, id); + try { + if (!response.accepted || !response.item) { + if (!response.accepted) { + updateConversationRuntimeEntry(conversationIdValue, (current) => ({ + ...current, + error: response.message || "queued edit failed", + })); + } + return; + } + const draft = JSON.parse(response.item.draftJson) as MentionComposerDraft; + const uploadedFiles = JSON.parse( + response.item.uploadedFilesJson, + ) as PendingUploadedFile[]; + queuedChatEditSessionRef.current = { + itemId: response.item.id, + revision: response.snapshot?.revision ?? chatQueueRevisionRef.current, + }; + composerRef.current?.setDraft(draft); + setPendingUploadsForConversation( + conversationIdValue, + Array.isArray(uploadedFiles) ? uploadedFiles : [], + ); + clearCachedComposerDraft(conversationIdValue); + applyChatQueueSnapshot(response.snapshot); + window.requestAnimationFrame(() => composerRef.current?.focus()); + } catch (error) { + throw new Error(asErrorMessage(error, "invalid queued edit payload")); + } + })().catch((error) => { + reportChatQueueActionError(conversationIdValue, error, "queued chat edit failed"); + }); } function removeQueuedTurn(id: string) { - setQueuedChatTurnsState((current) => removeQueuedChatTurn(current, id)); + const conversationIdValue = getDisplayedConversationId(); + if (!api || !conversationIdValue) return; + void api + .chatQueueRemove(conversationIdValue, id) + .then((response) => { + applyChatQueueSnapshot(response.snapshot); + }) + .catch((error) => { + reportChatQueueActionError(conversationIdValue, error, "queued chat remove failed"); + }); } function startNewConversation(options?: { workdir?: string }) { @@ -4846,6 +5002,28 @@ export default function GatewayApp() { if (!normalized && uploadedFiles.length === 0) { return; } + const optimisticEditUserEntryId = `edit-user-${crypto.randomUUID()}`; + const optimisticEditUserEntry: ChatEntry = { + id: optimisticEditUserEntryId, + kind: "user", + text: normalized, + attachments: uploadedFiles, + }; + const messageRefMatches = (candidate: HistoryMessageRef | undefined) => + Boolean(candidate) && + candidate?.segmentIndex === messageRef.segmentIndex && + candidate?.messageIndex === messageRef.messageIndex && + candidate?.segmentId === messageRef.segmentId && + candidate?.messageId === messageRef.messageId && + candidate?.role === messageRef.role && + candidate?.contentHash === messageRef.contentHash; + const buildPreviewMessages = (messages: ChatEntry[]) => { + const targetIndex = messages.findIndex( + (entry) => entry.kind === "user" && messageRefMatches(entry.messageRef), + ); + const prefix = targetIndex >= 0 ? messages.slice(0, targetIndex) : messages; + return [...prefix, optimisticEditUserEntry]; + }; const randomPart = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() @@ -4869,6 +5047,14 @@ export default function GatewayApp() { blockedHistoryHydrationConversationIdsRef.current.add(activeConversationId); invalidateHistoryLoad(); markVisibleConversationRevision(); + clearConversationLiveStream(activeConversationId); + updateConversationRuntimeEntry(activeConversationId, (current) => ({ + ...current, + error: null, + toolStatus: null, + toolStatusIsCompaction: false, + messages: buildPreviewMessages(current.messages), + })); try { const detail = await api.getHistoryPrefix(activeConversationId, messageRef, { @@ -4882,14 +5068,13 @@ export default function GatewayApp() { return; } const nextRuntime = createConversationRuntimeEntry({ - messages: entries, + messages: [...entries, optimisticEditUserEntry], error: null, toolStatus: null, toolStatusIsCompaction: false, isSending: false, }); - clearConversationLiveStream(activeConversationId); setSelectedHistory(detail); setSelectedHistoryEntries(entries); conversationRuntimeCacheRef.current.set(activeConversationId, nextRuntime); @@ -4908,6 +5093,8 @@ export default function GatewayApp() { uploadedFiles, editMessageRef: messageRef, clientRequestId, + optimisticUserEntryId: optimisticEditUserEntryId, + skipOptimisticUserEntry: true, }) ?? Promise.resolve(); await resendPromise; if (isCurrentEditTransaction()) { @@ -4917,7 +5104,20 @@ export default function GatewayApp() { }); } } catch (error) { - setChatError(asErrorMessage(error, "回溯历史消息失败")); + const message = asErrorMessage(error, "回溯历史消息失败"); + setChatError(message); + updateConversationRuntimeEntry(activeConversationId, (current) => ({ + ...current, + error: message, + toolStatus: null, + toolStatusIsCompaction: false, + isSending: false, + messages: current.messages.filter((entry) => entry.id !== optimisticEditUserEntryId), + })); + void refreshVisibleConversationHistorySnapshot(activeConversationId, api, { + allowIdle: true, + allowDuringEditTransaction: true, + }); } finally { if (isCurrentEditTransaction()) { activeEditResendTransactionsRef.current.delete(activeConversationId); @@ -4933,6 +5133,7 @@ export default function GatewayApp() { refreshVisibleConversationHistorySnapshot, syncVisibleConversationRuntime, updateHistoryItems, + updateConversationRuntimeEntry, ], ); @@ -5032,10 +5233,11 @@ export default function GatewayApp() { setHistoryDetailLoading(false); setHistoryMutating(false); queuedChatTurnsRef.current = []; - queuedChatProcessingConversationIdsRef.current.clear(); - queuedChatTurnEditSlotRef.current = null; - previousRunningConversationIdsRef.current = new Set(); + chatQueueConversationIdRef.current = ""; + chatQueueRevisionRef.current = 0; + queuedChatEditSessionRef.current = null; setQueuedChatTurns([]); + setChatQueueRevision(0); setLocalRunningConversationIds(new Set()); setRemoteRunningConversationIds(new Set()); setRemoteRunningConversationRuntime(new Map()); @@ -5203,20 +5405,6 @@ export default function GatewayApp() { return next; }, [historyItems, remoteRunningConversationRuntime, sidebarRunningConversationIds]); - useEffect(() => { - const currentRunningConversationIds = new Set(sidebarRunningConversationIds); - const previousRunningConversationIds = previousRunningConversationIdsRef.current; - previousRunningConversationIdsRef.current = currentRunningConversationIds; - for (const conversationIdValue of getQueuedConversationIds(queuedChatTurnsRef.current)) { - if ( - previousRunningConversationIds.has(conversationIdValue) && - !currentRunningConversationIds.has(conversationIdValue) - ) { - requestQueuedChatTurnProcessing(conversationIdValue); - } - } - }, [queuedChatTurns, sidebarRunningConversationIds]); - const projectActivityUpdatedAts = useMemo(() => { const updatedAts = buildWorkspaceProjectActivityUpdatedAts([ ...historyWorkdirs, @@ -5258,15 +5446,40 @@ export default function GatewayApp() { currentConversationRuntimeWorkdir || (isAgentMode ? activeWorkspaceProjectPath || settings.system.workdir.trim() : ""); displayedConversationWorkdirRef.current = displayedConversationWorkdir; + useEffect(() => { + if (!api || !displayedConversationId) { + queuedChatTurnsRef.current = []; + chatQueueConversationIdRef.current = ""; + chatQueueRevisionRef.current = 0; + setQueuedChatTurns([]); + setChatQueueRevision(0); + return; + } + if (chatQueueConversationIdRef.current !== displayedConversationId) { + queuedChatTurnsRef.current = []; + chatQueueConversationIdRef.current = displayedConversationId; + chatQueueRevisionRef.current = 0; + setQueuedChatTurns([]); + setChatQueueRevision(0); + } + let cancelled = false; + void api + .chatQueueGet(displayedConversationId) + .then((response) => { + if (!cancelled) applyChatQueueSnapshot(response.snapshot); + }) + .catch(() => undefined); + return () => { + cancelled = true; + }; + }, [api, applyChatQueueSnapshot, displayedConversationId]); const queuedChatTurnsForDisplayedConversation = useMemo( () => - queuedChatTurns - .filter((item) => item.conversationId === displayedConversationId) - .map((item) => ({ - id: item.id, - previewText: buildQueuedChatTurnPreview(item.draft), - fileCount: item.uploadedFiles.length, - })), + queuedChatTurns.map((item) => ({ + id: item.id, + previewText: item.previewText, + fileCount: item.fileCount, + })), [displayedConversationId, queuedChatTurns], ); const terminalProjectPath = isAgentMode ? activeWorkspaceProjectPath.trim() : ""; @@ -5459,6 +5672,9 @@ export default function GatewayApp() { displayedConversationId !== "" && remoteRunningConversationIds.has(displayedConversationId), ); + const observedRemoteRunKey = isObservingRemoteLiveConversation + ? resolveRunningConversationRunKey(remoteRunningConversationRuntime.get(displayedConversationId)) + : ""; useEffect(() => { const nextDisplayedConversationId = displayedConversationId.trim(); for (const conversationIdValue of [ @@ -5487,6 +5703,7 @@ export default function GatewayApp() { clearConversationLiveStream, displayedConversationId, isObservingRemoteLiveConversation, + observedRemoteRunKey, stopConversationEventStreamSubscription, ]); useEffect(() => { @@ -6066,8 +6283,30 @@ export default function GatewayApp() { ) { return; } - if (chatBusyRef.current || isObservingRemoteLiveConversation) { - enqueueCurrentComposerTurn("end"); + if (queuedChatEditSessionRef.current) { + submitInFlightRef.current = true; + void (async () => { + try { + await commitQueuedChatEdit(); + } finally { + submitInFlightRef.current = false; + } + })(); + return; + } + if ( + chatBusyRef.current || + isObservingRemoteLiveConversation || + queuedChatTurnsForDisplayedConversation.length > 0 + ) { + submitInFlightRef.current = true; + void (async () => { + try { + await submitCurrentComposerToGuiQueue("append"); + } finally { + submitInFlightRef.current = false; + } + })(); return; } submitInFlightRef.current = true; @@ -6149,7 +6388,6 @@ export default function GatewayApp() { isObservingRemoteLiveConversation ? displayedConversationId : undefined, ); }} - onInterruptAndSend={enqueueCurrentComposerTurnAndMaybeInterrupt} onPrepareChatRuntime={() => { if (!api || historyShareToken) { return; @@ -6172,7 +6410,7 @@ export default function GatewayApp() { }} queuedTurns={queuedChatTurnsForDisplayedConversation} onRunQueuedTurnNow={runQueuedTurnNow} - onMoveQueuedTurn={moveQueuedTurn} + onMoveQueuedTurnUp={moveQueuedTurnUp} onEditQueuedTurn={editQueuedTurn} onRemoveQueuedTurn={removeQueuedTurn} /> diff --git a/crates/agent-gateway/web/src/app/chatDraft.ts b/crates/agent-gateway/web/src/app/chatDraft.ts index 781721cf..76a933b6 100644 --- a/crates/agent-gateway/web/src/app/chatDraft.ts +++ b/crates/agent-gateway/web/src/app/chatDraft.ts @@ -5,6 +5,7 @@ import type { MentionComposerLargePaste, } from "@/components/chat/MentionComposer"; import type { PendingUploadedFile } from "@/lib/chat/uploadedFiles"; +import { formatFileMentionToken } from "@/lib/chat/mentionReferences"; import { withPastedTextDisplayMetadata } from "@/lib/chat/uploadedFiles"; import { importReadableFiles } from "@/lib/uploadReadableFiles"; @@ -57,6 +58,9 @@ export function buildTextFromComposerDraft( if (segment.type === "text") { return segment.text; } + if (segment.type === "fileMention") { + return formatFileMentionToken(segment.reference); + } if (segment.type === "skillMention") { return `$${segment.skill.name}`; } diff --git a/crates/agent-gateway/web/src/app/chatEventUtils.ts b/crates/agent-gateway/web/src/app/chatEventUtils.ts index a34fe163..818f9319 100644 --- a/crates/agent-gateway/web/src/app/chatEventUtils.ts +++ b/crates/agent-gateway/web/src/app/chatEventUtils.ts @@ -114,6 +114,7 @@ export function isChatControlEvent(event: ChatEvent): event is ChatControlEvent case "delivered": case "claimed": case "starting": + case "queued_in_gui": case "started": case "progress": case "completed": @@ -139,13 +140,14 @@ export function isPreparingChatControlEvent(event: ChatEvent) { event.state === "delivered" || event.state === "claimed" || event.state === "starting" || + event.state === "desktop_queued" || event.type === "accepted" || - event.type === "user_message" || event.type === "rebased" || event.type === "projection_updated" || event.type === "delivered" || event.type === "claimed" || event.type === "starting" || + event.type === "queued_in_gui" || event.type === "progress") ); } diff --git a/crates/agent-gateway/web/src/app/types.ts b/crates/agent-gateway/web/src/app/types.ts index 748d7507..baa2cbca 100644 --- a/crates/agent-gateway/web/src/app/types.ts +++ b/crates/agent-gateway/web/src/app/types.ts @@ -32,7 +32,6 @@ export type RunningConversationRuntime = { runId?: string; workdir?: string; firstSeq?: number; - latestSeq?: number; runEpoch?: number; updatedAt: number; }; @@ -49,6 +48,9 @@ export type SendChatOptions = { runtimeControls?: ChatRuntimeControls; workdir?: string; editMessageRef?: HistoryMessageRef; + optimisticUserEntryId?: string; + skipOptimisticUserEntry?: boolean; + queuePolicy?: "auto" | "append" | "interrupt"; }; export type SendChatFn = (message: string, options?: SendChatOptions) => Promise; diff --git a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx index 96d6350f..4ab188c4 100644 --- a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx +++ b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx @@ -625,6 +625,43 @@ function splitUserAttachmentsForDisplay(files: PendingUploadedFile[], text: stri }; } +function GatewayUserMessageBubbleBody(props: { + text: string; + attachments: PendingUploadedFile[]; + workspaceRoot?: string; + onLoadUploadedImagePreview?: UploadedImagePreviewLoader; + loadCommitDetails?: CommitDetailsLoader; +}) { + const { + text, + attachments, + workspaceRoot, + onLoadUploadedImagePreview, + loadCommitDetails, + } = props; + const { visibleFiles, pastedTextFiles } = splitUserAttachmentsForDisplay( + attachments, + text, + ); + + return ( +
+ + {text ? ( + + ) : null} +
+ ); +} + const MIN_EDIT_BUBBLE_HEIGHT_PX = 72; function resizeEditableTextarea(textarea: HTMLTextAreaElement | null) { @@ -721,6 +758,56 @@ const EditableUserMessageBubble = memo(function EditableUserMessageBubble(props: ); }); +function useGatewayCommitDetailsLoader( + workspaceRoot?: string, + gitClient?: GitClient | null, + cacheResetKey?: string, +) { + const commitDetailsCacheRef = useRef(new Map()); + + useEffect(() => { + if (cacheResetKey !== undefined) { + commitDetailsCacheRef.current.clear(); + } + }, [cacheResetKey]); + + return useCallback( + async (commit) => { + const workdir = workspaceRoot?.trim() ?? ""; + const sha = commit.sha.trim(); + if (!gitClient || !workdir || !sha) return null; + const cacheKey = `${workdir}\u0000${sha}`; + const cached = commitDetailsCacheRef.current.get(cacheKey); + if (cached) return cached; + const response = await gitClient.commitDetails(workdir, sha); + const details = response.commit; + const resolved: CommitDisplayReference = { + sha: details.sha, + shortSha: details.shortSha, + subject: details.subject, + body: details.body, + authorName: details.authorName, + authorEmail: details.authorEmail, + authorDate: details.authorDate, + fileCount: details.fileCount, + filesChanged: details.filesChanged, + insertions: details.insertions, + deletions: details.deletions, + stat: details.stat, + remoteName: details.remoteName, + remoteUrl: details.remoteUrl, + githubUrl: + commit.githubUrl || + buildGitHubCommitUrl(details.remoteUrl || response.state.remoteUrl, details.sha) || + undefined, + }; + commitDetailsCacheRef.current.set(cacheKey, resolved); + return resolved; + }, + [gitClient, workspaceRoot], + ); +} + const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { conversationId?: string; items: GatewayTranscriptItem[]; @@ -762,48 +849,15 @@ const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { const { locale, t } = useLocale(); const [copiedMessageId, setCopiedMessageId] = useState(null); const [editingMessageId, setEditingMessageId] = useState(null); - const commitDetailsCacheRef = useRef(new Map()); const historyIdentityKey = `${conversationId ?? ""}\n${items[0]?.id ?? ""}`; - - const loadCommitDetails = useCallback( - async (commit) => { - const workdir = workspaceRoot?.trim() ?? ""; - const sha = commit.sha.trim(); - if (!gitClient || !workdir || !sha) return null; - const cacheKey = `${workdir}\u0000${sha}`; - const cached = commitDetailsCacheRef.current.get(cacheKey); - if (cached) return cached; - const response = await gitClient.commitDetails(workdir, sha); - const details = response.commit; - const resolved: CommitDisplayReference = { - sha: details.sha, - shortSha: details.shortSha, - subject: details.subject, - body: details.body, - authorName: details.authorName, - authorEmail: details.authorEmail, - authorDate: details.authorDate, - fileCount: details.fileCount, - filesChanged: details.filesChanged, - insertions: details.insertions, - deletions: details.deletions, - stat: details.stat, - remoteName: details.remoteName, - remoteUrl: details.remoteUrl, - githubUrl: - commit.githubUrl || - buildGitHubCommitUrl(details.remoteUrl || response.state.remoteUrl, details.sha) || - undefined, - }; - commitDetailsCacheRef.current.set(cacheKey, resolved); - return resolved; - }, - [gitClient, workspaceRoot], + const loadCommitDetails = useGatewayCommitDetailsLoader( + workspaceRoot, + gitClient, + historyIdentityKey, ); useEffect(() => { setEditingMessageId(null); - commitDetailsCacheRef.current.clear(); }, [historyIdentityKey]); useEffect(() => { @@ -891,10 +945,6 @@ const GatewayTranscriptHistory = memo(function GatewayTranscriptHistory(props: { ? "This older message cannot be edited because it has no stable message identifier." : "旧历史缺少稳定消息标识,无法编辑重发" : t("chat.edit"); - const { visibleFiles, pastedTextFiles } = splitUserAttachmentsForDisplay( - item.attachments, - item.text, - ); return (
) : (
-
- - {item.text ? ( - - ) : null} -
+ {!readOnly ? (
- - - - - - - - - - - - - -
- - ))} - +
+ + +
+
+
+
    + {queuedTurns.map((item, index) => ( +
  • +
    + {index > 0 ? ( + + ) : ( + + )} + +
    +
    + + {item.previewText || t("chat.queue.emptyMessage")} + + {item.fileCount > 0 ? ( + + {t("chat.queue.fileCount").replace("{count}", String(item.fileCount))} + + ) : null} +
    +
    + + + + + + + + + +
    +
  • + ))} +
+
+
) : null} @@ -515,52 +633,40 @@ export const ChatComposerBar = memo(function ChatComposerBar(props: {
- {isSending ? ( - <> - - - - ) : null}
diff --git a/crates/agent-gateway/web/src/pages/chat/queue/chatTurnQueue.ts b/crates/agent-gateway/web/src/pages/chat/queue/chatTurnQueue.ts index 0063ab0e..8b388848 100644 --- a/crates/agent-gateway/web/src/pages/chat/queue/chatTurnQueue.ts +++ b/crates/agent-gateway/web/src/pages/chat/queue/chatTurnQueue.ts @@ -1,6 +1,7 @@ import type { MentionComposerDraft } from "@/components/chat/MentionComposer"; import type { PendingUploadedFile } from "@/lib/chat/uploadedFiles"; import type { ChatRuntimeControls } from "@/lib/settings"; +import { fileMentionDisplayName } from "@/lib/chat/mentionReferences"; export type QueuedChatTurn = { id: string; @@ -49,6 +50,8 @@ export function buildQueuedChatTurnPreview(draft: MentionComposerDraft) { switch (segment.type) { case "largePaste": return segment.paste.label; + case "fileMention": + return fileMentionDisplayName(segment.reference); case "skillMention": return `$${segment.skill.name}`; case "commitMention": diff --git a/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs b/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs index 2e03fa4c..d6e0f059 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs +++ b/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs @@ -4,9 +4,9 @@ use serde_json::Value; use crate::commands::settings::{load_remote_settings, open_db, parse_remote_settings_payload}; use crate::services::gateway::{ - build_history_sync_activity, GatewayChatClaimedRequest, GatewayController, - GatewayStatusSnapshot, GatewayTunnelCreateInput, GatewayTunnelSummary, - GatewayTunnelUpdateInput, + build_history_sync_activity, GatewayChatClaimedRequest, GatewayChatQueueEventInput, + GatewayChatQueueResponseInput, GatewayController, GatewayStatusSnapshot, + GatewayTunnelCreateInput, GatewayTunnelSummary, GatewayTunnelUpdateInput, }; #[tauri::command] @@ -76,6 +76,29 @@ pub async fn gateway_chat_mark_started( .await } +#[tauri::command(rename_all = "snake_case")] +pub async fn gateway_chat_mark_local_started( + request_id: String, + conversation_id: String, + gateway_controller: tauri::State<'_, Arc>, +) -> Result<(), String> { + gateway_controller + .mark_local_chat_run_started(request_id, conversation_id) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn gateway_chat_mark_queued_in_gui( + request_id: String, + conversation_id: String, + worker_id: String, + gateway_controller: tauri::State<'_, Arc>, +) -> Result<(), String> { + gateway_controller + .mark_chat_request_queued_in_gui(request_id, conversation_id, worker_id) + .await +} + #[tauri::command(rename_all = "snake_case")] pub async fn gateway_chat_complete( request_id: String, @@ -110,6 +133,18 @@ pub async fn gateway_chat_fail( .await } +#[tauri::command(rename_all = "snake_case")] +pub async fn gateway_chat_cancel_request( + request_id: String, + conversation_id: String, + worker_id: String, + gateway_controller: tauri::State<'_, Arc>, +) -> Result<(), String> { + gateway_controller + .cancel_chat_request(request_id, conversation_id, worker_id) + .await +} + #[tauri::command(rename_all = "snake_case")] pub fn gateway_chat_heartbeat( request_id: String, @@ -141,6 +176,22 @@ pub fn gateway_chat_release_lease( gateway_controller.release_chat_request_lease(request_id, worker_id) } +#[tauri::command(rename_all = "snake_case")] +pub fn gateway_chat_queue_respond( + input: GatewayChatQueueResponseInput, + gateway_controller: tauri::State<'_, Arc>, +) -> Result<(), String> { + gateway_controller.respond_chat_queue_request(input) +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn gateway_publish_chat_queue_event( + input: GatewayChatQueueEventInput, + gateway_controller: tauri::State<'_, Arc>, +) -> Result<(), String> { + gateway_controller.publish_chat_queue_event(input).await +} + #[tauri::command(rename_all = "snake_case")] pub async fn gateway_publish_conversation_activity( conversation_id: String, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs b/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs index c42c9c99..7e8ee7af 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs @@ -52,7 +52,9 @@ enum FsError { #[error("workdir must be an existing absolute directory: {0}")] InvalidWorkdir(String), - #[error("resolved path segment must not contain .., drive letters, or an absolute root path: {0}")] + #[error( + "resolved path segment must not contain .., drive letters, or an absolute root path: {0}" + )] InvalidRelPath(String), #[error("Target path is outside the resolved directory: {0}")] diff --git a/crates/agent-gui/src-tauri/src/lib.rs b/crates/agent-gui/src-tauri/src/lib.rs index e383f6cf..789f2e29 100644 --- a/crates/agent-gui/src-tauri/src/lib.rs +++ b/crates/agent-gui/src-tauri/src/lib.rs @@ -222,11 +222,16 @@ macro_rules! app_invoke_handler { commands::gateway::gateway_send_chat_event, commands::gateway::gateway_chat_claim_next, commands::gateway::gateway_chat_mark_started, + commands::gateway::gateway_chat_mark_local_started, + commands::gateway::gateway_chat_mark_queued_in_gui, commands::gateway::gateway_chat_complete, commands::gateway::gateway_chat_fail, + commands::gateway::gateway_chat_cancel_request, commands::gateway::gateway_chat_heartbeat, commands::gateway::gateway_chat_runtime_heartbeat, commands::gateway::gateway_chat_release_lease, + commands::gateway::gateway_chat_queue_respond, + commands::gateway::gateway_publish_chat_queue_event, commands::gateway::gateway_publish_conversation_activity, commands::gateway::gateway_publish_settings_sync, commands::gateway::gateway_tunnel_list, diff --git a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs b/crates/agent-gui/src-tauri/src/runtime/managed_process.rs index f4f4884f..dd2e5e07 100644 --- a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs +++ b/crates/agent-gui/src-tauri/src/runtime/managed_process.rs @@ -8,9 +8,7 @@ use std::sync::Mutex; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::runtime::platform::expand_tilde_path; -use crate::runtime::process::{ - kill_child_process_tree_best_effort, terminate_child_process_tree, -}; +use crate::runtime::process::{kill_child_process_tree_best_effort, terminate_child_process_tree}; use crate::runtime::shell_runner::spawn_platform_shell_command; const PROCESS_LOG_DIR: &str = "process-logs"; diff --git a/crates/agent-gui/src-tauri/src/runtime/shell_runner.rs b/crates/agent-gui/src-tauri/src/runtime/shell_runner.rs index 44609530..bcf60604 100644 --- a/crates/agent-gui/src-tauri/src/runtime/shell_runner.rs +++ b/crates/agent-gui/src-tauri/src/runtime/shell_runner.rs @@ -552,8 +552,8 @@ where let mut errors: Vec = Vec::new(); for candidate in platform_shell_candidates(command) { - let (stdout, stderr) = stdio_factory() - .map_err(|err| format!("Failed to prepare shell stdio: {err}"))?; + let (stdout, stderr) = + stdio_factory().map_err(|err| format!("Failed to prepare shell stdio: {err}"))?; let mut c = Command::new(&candidate.program); c.args(&candidate.args); if candidate.augment_macos_path { @@ -576,8 +576,7 @@ where } Err(err) => errors.push(format!( "{} ({}) failed: {err}", - candidate.profile.profile, - candidate.profile.display_shell + candidate.profile.profile, candidate.profile.display_shell )), } } @@ -629,9 +628,8 @@ pub(crate) fn run_shell_script( let timeout = Duration::from_millis(effective_timeout_ms); let start = Instant::now(); - let spawned = spawn_platform_shell_command(cmd, &actual_cwd, || { - Ok((Stdio::piped(), Stdio::piped())) - })?; + let spawned = + spawn_platform_shell_command(cmd, &actual_cwd, || Ok((Stdio::piped(), Stdio::piped())))?; let mut child = spawned.child; let shell_profile = spawned.profile; let shell_name = shell_basename(shell_profile.display_shell); diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal.rs b/crates/agent-gui/src-tauri/src/runtime/terminal.rs index e31fb563..776b194a 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal.rs @@ -575,9 +575,7 @@ impl TerminalEchoDispatchState { } fn is_empty(&self) -> bool { - self.pending.is_empty() - && self.deferred_local.is_empty() - && self.deferred_remote.is_empty() + self.pending.is_empty() && self.deferred_local.is_empty() && self.deferred_remote.is_empty() } } @@ -2205,11 +2203,7 @@ impl TerminalSessionRegistry { self.broadcast_output(&entry, data, output_start_offset, output_end_offset); } - fn record_input_echo_candidates( - &self, - session_id: &str, - echo_bytes: Vec, - ) { + fn record_input_echo_candidates(&self, session_id: &str, echo_bytes: Vec) { if echo_bytes.is_empty() { return; } diff --git a/crates/agent-gui/src-tauri/src/services/gateway.rs b/crates/agent-gui/src-tauri/src/services/gateway.rs index 48b9e35e..acec6f93 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway.rs @@ -7,10 +7,10 @@ use std::thread; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use futures_util::{SinkExt, StreamExt}; -use reqwest::Url; use reqwest::header::{HeaderName, HeaderValue}; +use reqwest::Url; use serde::{Deserialize, Serialize}; -use serde_json::{Value, json}; +use serde_json::{json, Value}; use tauri::Emitter; use tokio::sync::{mpsc, oneshot, watch}; use tokio_stream::wrappers::ReceiverStream; @@ -21,10 +21,10 @@ use uuid::Uuid; use crate::commands::chat_history::{self, ChatHistorySummary}; use crate::commands::settings::{ - PROVIDER_API_KEY_UPDATES_FIELD, RemoteSettingsPayload, SSH_PATCH_FIELD, - SSH_SECRET_UPDATES_FIELD, apply_ssh_patch_with_conn, load_gateway_settings_sync_snapshot, - load_remote_settings, normalize_remote_settings_payload, open_db, - redact_gateway_settings_sync_payload, reset_runtime_ssh_known_host, + apply_ssh_patch_with_conn, load_gateway_settings_sync_snapshot, load_remote_settings, + normalize_remote_settings_payload, open_db, redact_gateway_settings_sync_payload, + reset_runtime_ssh_known_host, RemoteSettingsPayload, PROVIDER_API_KEY_UPDATES_FIELD, + SSH_PATCH_FIELD, SSH_SECRET_UPDATES_FIELD, }; use crate::runtime::project_path::{ project_path_key as normalize_project_path_key, project_path_keys_equal, @@ -34,10 +34,9 @@ use crate::runtime::sftp::{ SftpStatResponse, SftpTransferResponse, SftpTransferState, }; use crate::runtime::terminal::{ - SshTerminalTabRecord, SshTerminalTabsSnapshot, TerminalEventPayload, TerminalSessionRecord, - TerminalSessionRegistry, TerminalShellOption, TerminalSnapshotResponse, + terminal_shell_options, SshTerminalTabRecord, SshTerminalTabsSnapshot, TerminalEventPayload, + TerminalSessionRecord, TerminalSessionRegistry, TerminalShellOption, TerminalSnapshotResponse, TerminalSshCreateResponse, TerminalStreamEventPayload, TerminalStreamSnapshotResponse, - terminal_shell_options, }; use crate::services::cron::CronManager; use crate::services::gateway_bridge; @@ -183,6 +182,7 @@ pub struct GatewayChatRequestEvent { pub workdir: String, pub selected_system_tools: Vec, pub uploaded_files: Vec, + pub queue_policy: String, } fn is_complete_user_chat_message_ref(ref_value: &proto::ChatMessageRef) -> bool { @@ -201,6 +201,47 @@ struct GatewayChatCancelEvent { conversation_id: String, } +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GatewayChatQueueRequestEvent { + pub request_id: String, + pub action: String, + pub conversation_id: String, + pub item_id: String, + pub direction: String, + pub revision: u64, + pub draft_json: String, + pub uploaded_files_json: String, + pub request_json: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GatewayChatQueueResponseInput { + pub request_id: String, + #[serde(default)] + pub accepted: bool, + #[serde(default)] + pub message: String, + #[serde(default)] + pub snapshot_json: String, + #[serde(default)] + pub item_json: String, + #[serde(default)] + pub error_code: String, + #[serde(default)] + pub revision: u64, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GatewayChatQueueEventInput { + pub conversation_id: String, + pub snapshot_json: String, + #[serde(default)] + pub revision: u64, +} + #[derive(Debug, Clone)] struct RemoteChatInboxRecord { request: GatewayChatRequestEvent, @@ -280,6 +321,7 @@ pub struct GatewayController { tunnel_http_streams: Mutex>, tunnel_ws_streams: Mutex>>, pending_tunnel_controls: Mutex>>, + pending_chat_queue_requests: Mutex>>, terminal_forwarder_once: Once, terminal_stream_forwarder_once: Once, sftp_forwarder_once: Once, @@ -323,6 +365,7 @@ impl GatewayController { tunnel_http_streams: Mutex::new(HashMap::new()), tunnel_ws_streams: Mutex::new(HashMap::new()), pending_tunnel_controls: Mutex::new(HashMap::new()), + pending_chat_queue_requests: Mutex::new(HashMap::new()), terminal_forwarder_once: Once::new(), terminal_stream_forwarder_once: Once::new(), sftp_forwarder_once: Once::new(), @@ -1080,6 +1123,9 @@ impl GatewayController { Some(proto::gateway_envelope::Payload::ChatCommand(command)) => { self.handle_chat_command(request_id, command).await } + Some(proto::gateway_envelope::Payload::ChatQueue(request)) => { + self.handle_chat_queue_request(request_id, request).await + } Some(proto::gateway_envelope::Payload::CronManage(request)) => { let should_refresh_settings = matches!(request.action.trim(), "create" | "update" | "delete"); @@ -2555,6 +2601,7 @@ impl GatewayController { workdir, selected_system_tools, uploaded_files, + queue_policy, } = request; let selected_model = selected_model.map(|selected_model| GatewaySelectedModelEvent { custom_provider_id: selected_model.custom_provider_id, @@ -2598,6 +2645,7 @@ impl GatewayController { size_bytes: file.size_bytes, }) .collect(), + queue_policy, } } @@ -2736,7 +2784,10 @@ impl GatewayController { inbox.remove(request_id); } if !conversation_id.is_empty() { - inbox.retain(|_, record| record.request.conversation_id.trim() != conversation_id); + inbox.retain(|_, record| { + record.request.conversation_id.trim() != conversation_id + || !Self::remote_chat_record_should_cancel_for_conversation(record) + }); } Ok(()) } @@ -2776,6 +2827,7 @@ impl GatewayController { match record.state.trim() { "claimed" => "claimed", "starting" => "starting", + "queued_in_gui" => "queued_in_gui", "running" => "started", "failed" => "failed", "cancelled" => "cancelled", @@ -2801,6 +2853,13 @@ impl GatewayController { } } + fn remote_chat_record_should_cancel_for_conversation(record: &RemoteChatInboxRecord) -> bool { + if record.started { + return true; + } + matches!(record.state.trim(), "claimed" | "starting" | "running") + } + fn remote_chat_record_has_current_lease( record: &RemoteChatInboxRecord, worker_id: &str, @@ -2954,7 +3013,10 @@ impl GatewayController { let record = inbox .get_mut(&request_id) .ok_or_else(|| "remote chat request lease is no longer active".to_string())?; - if !Self::remote_chat_record_has_current_lease(record, &worker_id, now) { + let queued_in_gui = record.state.trim() == "queued_in_gui" && !record.started; + if !queued_in_gui + && !Self::remote_chat_record_has_current_lease(record, &worker_id, now) + { return Err("remote chat request lease is no longer active".to_string()); } if record.started { @@ -2962,6 +3024,7 @@ impl GatewayController { } record.state = "running".to_string(); record.started = true; + record.lease_owner = Some(worker_id); if !conversation_id.is_empty() { record.request.conversation_id = conversation_id.clone(); } @@ -2973,6 +3036,59 @@ impl GatewayController { .await } + pub async fn mark_local_chat_run_started( + &self, + request_id: String, + conversation_id: String, + ) -> Result<(), String> { + let request_id = request_id.trim().to_string(); + let conversation_id = conversation_id.trim().to_string(); + if request_id.is_empty() || conversation_id.is_empty() { + return Ok(()); + } + self.send_gateway_chat_control_event(request_id, conversation_id, "started") + .await + } + + pub async fn mark_chat_request_queued_in_gui( + &self, + request_id: String, + conversation_id: String, + worker_id: String, + ) -> Result<(), String> { + let request_id = request_id.trim().to_string(); + let conversation_id = conversation_id.trim().to_string(); + let worker_id = worker_id.trim().to_string(); + let should_send = { + let mut inbox = self + .remote_chat_inbox + .lock() + .map_err(|_| "gateway remote chat inbox lock poisoned".to_string())?; + let Some(record) = inbox.get_mut(&request_id) else { + return Ok(()); + }; + if record.started { + return Ok(()); + } + if !Self::remote_chat_record_is_owned_by_worker(record, &worker_id) { + return Ok(()); + } + record.state = "queued_in_gui".to_string(); + record.lease_owner = None; + record.lease_expires_at = None; + if !conversation_id.is_empty() { + record.request.conversation_id = conversation_id.clone(); + } + record.updated_at = Instant::now(); + true + }; + if !should_send { + return Ok(()); + } + self.send_gateway_chat_control_event(request_id, conversation_id, "queued_in_gui") + .await + } + pub async fn complete_chat_request( &self, request_id: String, @@ -3023,7 +3139,8 @@ impl GatewayController { let Some(record) = inbox.get_mut(&request_id) else { return Ok(()); }; - if !Self::remote_chat_record_is_owned_by_worker(record, &worker_id) { + let queued_in_gui = terminal && record.state.trim() == "queued_in_gui"; + if !queued_in_gui && !Self::remote_chat_record_is_owned_by_worker(record, &worker_id) { return Ok(()); } record.state = if terminal { "failed" } else { "queued" }.to_string(); @@ -3049,6 +3166,37 @@ impl GatewayController { .await } + pub async fn cancel_chat_request( + &self, + request_id: String, + conversation_id: String, + worker_id: String, + ) -> Result<(), String> { + let request_id = request_id.trim().to_string(); + let conversation_id = conversation_id.trim().to_string(); + let worker_id = worker_id.trim().to_string(); + let should_send = { + let mut inbox = self + .remote_chat_inbox + .lock() + .map_err(|_| "gateway remote chat inbox lock poisoned".to_string())?; + let Some(record) = inbox.get(&request_id) else { + return Ok(()); + }; + let queued_in_gui = record.state.trim() == "queued_in_gui"; + if !queued_in_gui && !Self::remote_chat_record_is_owned_by_worker(record, &worker_id) { + return Ok(()); + } + inbox.remove(&request_id); + true + }; + if !should_send { + return Ok(()); + } + self.send_gateway_chat_control_event(request_id, conversation_id, "cancelled") + .await + } + pub fn heartbeat_chat_request( &self, request_id: String, @@ -3209,6 +3357,132 @@ impl GatewayController { .await } + async fn handle_chat_queue_request( + self: &Arc, + request_id: String, + request: proto::ChatQueueRequest, + ) -> Result<(), String> { + let event_payload = GatewayChatQueueRequestEvent { + request_id: request_id.clone(), + action: request.action, + conversation_id: request.conversation_id, + item_id: request.item_id, + direction: request.direction, + revision: request.revision, + draft_json: request.draft_json, + uploaded_files_json: request.uploaded_files_json, + request_json: request.request_json, + }; + + let (tx, rx) = oneshot::channel(); + self.pending_chat_queue_requests + .lock() + .map_err(|_| "gateway chat queue request lock poisoned".to_string())? + .insert(request_id.clone(), tx); + + if let Err(error) = self + .app_handle + .emit("gateway:chat-queue-request", event_payload) + { + let _ = self + .pending_chat_queue_requests + .lock() + .map(|mut pending| pending.remove(&request_id)); + return self + .send_chat_queue_response( + request_id, + proto::ChatQueueResponse { + accepted: false, + message: format!("emit gateway chat queue request failed: {error}"), + error_code: "emit_failed".to_string(), + ..Default::default() + }, + ) + .await; + } + + let response = match tokio::time::timeout(Duration::from_secs(30), rx).await { + Ok(Ok(response)) => response, + Ok(Err(_)) => proto::ChatQueueResponse { + accepted: false, + message: "chat queue response dropped".to_string(), + error_code: "response_dropped".to_string(), + ..Default::default() + }, + Err(_) => { + let _ = self + .pending_chat_queue_requests + .lock() + .map(|mut pending| pending.remove(&request_id)); + proto::ChatQueueResponse { + accepted: false, + message: "chat queue request timed out".to_string(), + error_code: "timeout".to_string(), + ..Default::default() + } + } + }; + + self.send_chat_queue_response(request_id, response).await + } + + async fn send_chat_queue_response( + &self, + request_id: String, + response: proto::ChatQueueResponse, + ) -> Result<(), String> { + self.send_agent_envelope(proto::AgentEnvelope { + request_id, + timestamp: now_unix_seconds(), + payload: Some(proto::agent_envelope::Payload::ChatQueueResp(response)), + }) + .await + } + + pub fn respond_chat_queue_request( + &self, + input: GatewayChatQueueResponseInput, + ) -> Result<(), String> { + let request_id = input.request_id.trim().to_string(); + if request_id.is_empty() { + return Err("chat queue request_id is required".to_string()); + } + let sender = self + .pending_chat_queue_requests + .lock() + .map_err(|_| "gateway chat queue request lock poisoned".to_string())? + .remove(&request_id); + if let Some(sender) = sender { + let _ = sender.send(proto::ChatQueueResponse { + accepted: input.accepted, + message: input.message, + snapshot_json: input.snapshot_json, + item_json: input.item_json, + error_code: input.error_code, + revision: input.revision, + }); + } + Ok(()) + } + + pub async fn publish_chat_queue_event( + &self, + input: GatewayChatQueueEventInput, + ) -> Result<(), String> { + self.send_agent_envelope(proto::AgentEnvelope { + request_id: format!("chat-queue-event-{}", Uuid::new_v4()), + timestamp: now_unix_seconds(), + payload: Some(proto::agent_envelope::Payload::ChatQueueEvent( + proto::ChatQueueEvent { + conversation_id: input.conversation_id, + snapshot_json: input.snapshot_json, + revision: input.revision, + }, + )), + }) + .await + } + async fn send_tunnel_control_request( &self, request: proto::TunnelControlRequest, @@ -4848,16 +5122,17 @@ fn serialize_settings_sync_payload(payload: &Value) -> Result { #[cfg(test)] mod tests { use super::{ - GATEWAY_CHAT_LEASE_MS, GATEWAY_CHAT_RUNNING_LEASE_MS, GatewayChatRequestEvent, - GatewayController, GatewayStatusSnapshot, RemoteChatInboxRecord, build_chat_event_envelope, - build_endpoint, build_grpc_url, build_local_settings_update_event_payload, - build_tunnel_upstream_url, format_gateway_terminal_stream_rpc_error, - history_share_resolve_error_code, merge_settings_sync_snapshot, normalize_tunnel_ttl, - proto, queue_terminal_stream_handshake_frame, required_terminal_project_path_key, + build_chat_event_envelope, build_endpoint, build_grpc_url, + build_local_settings_update_event_payload, build_tunnel_upstream_url, + format_gateway_terminal_stream_rpc_error, history_share_resolve_error_code, + merge_settings_sync_snapshot, normalize_tunnel_ttl, proto, + queue_terminal_stream_handshake_frame, required_terminal_project_path_key, set_disconnected_status, tunnel_expires_at, validate_tunnel_target_url, + GatewayChatRequestEvent, GatewayController, GatewayStatusSnapshot, RemoteChatInboxRecord, + GATEWAY_CHAT_LEASE_MS, GATEWAY_CHAT_RUNNING_LEASE_MS, }; use crate::commands::settings::RemoteSettingsPayload; - use serde_json::{Value, json}; + use serde_json::{json, Value}; use std::time::{Duration, Instant}; fn gateway_chat_request( @@ -4879,6 +5154,7 @@ mod tests { workdir: String::new(), selected_system_tools: Vec::new(), uploaded_files: Vec::new(), + queue_policy: String::new(), } } @@ -5040,6 +5316,42 @@ mod tests { )); } + #[test] + fn conversation_cancel_preserves_gui_queued_remote_requests() { + let now = Instant::now(); + let queued_in_gui = remote_chat_record( + gateway_chat_request("request-1", "client-1", "conversation-1", "first"), + "queued_in_gui", + false, + now, + ); + let queued = remote_chat_record( + gateway_chat_request("request-2", "client-2", "conversation-1", "second"), + "queued", + false, + now, + ); + let claimed = remote_chat_record( + gateway_chat_request("request-3", "client-3", "conversation-1", "third"), + "claimed", + false, + now, + ); + let running = remote_chat_record( + gateway_chat_request("request-4", "client-4", "conversation-1", "fourth"), + "running", + true, + now, + ); + + assert!( + !GatewayController::remote_chat_record_should_cancel_for_conversation(&queued_in_gui) + ); + assert!(!GatewayController::remote_chat_record_should_cancel_for_conversation(&queued)); + assert!(GatewayController::remote_chat_record_should_cancel_for_conversation(&claimed)); + assert!(GatewayController::remote_chat_record_should_cancel_for_conversation(&running)); + } + #[test] fn history_share_resolve_error_code_maps_public_share_failures() { assert_eq!(history_share_resolve_error_code("分享 token 不能为空"), 400); @@ -5437,6 +5749,45 @@ mod tests { assert_eq!(data["updatedAt"], 1234); assert_eq!(data["round"], 2); } + + #[test] + fn build_chat_event_envelope_preserves_user_message_payload() { + let envelope = build_chat_event_envelope( + "request-1".to_string(), + json!({ + "type": "user_message", + "conversation_id": "conversation-1", + "message": "queued prompt", + "uploaded_files": [ + { + "relativePath": "notes.md", + "absolutePath": "/workspace/notes.md", + "fileName": "notes.md", + "kind": "text", + "sizeBytes": 12 + } + ], + "execution_mode": "agent" + }), + ) + .expect("build user message event envelope"); + + let chat_event = match envelope.payload.expect("payload") { + super::proto::agent_envelope::Payload::ChatEvent(event) => event, + _ => panic!("expected chat event payload"), + }; + assert_eq!(chat_event.conversation_id, "conversation-1"); + assert_eq!( + chat_event.r#type, + super::proto::chat_event::ChatEventType::UserMessage as i32 + ); + + let data: Value = serde_json::from_str(&chat_event.data).expect("chat event data"); + assert_eq!(data["message"], "queued prompt"); + assert_eq!(data["uploaded_files"][0]["relativePath"], "notes.md"); + assert_eq!(data["uploaded_files"][0]["kind"], "text"); + assert_eq!(data["execution_mode"], "agent"); + } } fn build_chat_event_envelope( @@ -5509,6 +5860,36 @@ fn build_chat_event_envelope( "round": optional_number_field(object, "round"), }), ), + "user_message" => ( + proto::chat_event::ChatEventType::UserMessage as i32, + json!({ + "message": required_raw_string_field(object, "message")?, + "uploaded_files": object.get("uploaded_files") + .or_else(|| object.get("uploadedFiles")) + .cloned() + .unwrap_or(Value::Null), + "execution_mode": optional_string_field(object, "execution_mode") + .or_else(|| optional_string_field(object, "executionMode")), + "workdir": optional_string_field(object, "workdir"), + "selected_system_tools": object.get("selected_system_tools") + .or_else(|| object.get("selectedSystemTools")) + .cloned() + .unwrap_or(Value::Null), + "runtime_controls": object.get("runtime_controls") + .or_else(|| object.get("runtimeControls")) + .cloned() + .unwrap_or(Value::Null), + "selected_model": object.get("selected_model") + .or_else(|| object.get("selectedModel")) + .cloned() + .unwrap_or(Value::Null), + "base_message_ref": object.get("base_message_ref") + .or_else(|| object.get("baseMessageRef")) + .cloned() + .unwrap_or(Value::Null), + "reason": optional_string_field(object, "reason"), + }), + ), "done" => ( proto::chat_event::ChatEventType::Done as i32, json!({ diff --git a/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs b/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs index 8b46fa45..98e35ad2 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, sync::Arc}; use serde::Deserialize; -use serde_json::{Value, json}; +use serde_json::{json, Value}; use crate::commands::{ chat_history, @@ -13,13 +13,13 @@ use crate::commands::{ git::git_gateway_action_sync, settings::{load_providers, open_db}, system::{ - SystemReadableFileUploadInput, system_create_project_folder_sync, - system_import_uploaded_readable_files_sync, system_list_skill_files_sync, - system_manage_cron_task_sync, system_read_skill_metadata_sync, system_read_skill_text_sync, - system_read_uploaded_image_preview_sync, + system_create_project_folder_sync, system_import_uploaded_readable_files_sync, + system_list_skill_files_sync, system_manage_cron_task_sync, + system_read_skill_metadata_sync, system_read_skill_text_sync, + system_read_uploaded_image_preview_sync, SystemReadableFileUploadInput, }, }; -use crate::services::cron::{CronManager, clear_logs_sync, list_logs_sync}; +use crate::services::cron::{clear_logs_sync, list_logs_sync, CronManager}; use crate::services::gateway::proto; use crate::services::memory::{ MemoryAcceptArgs, MemoryBatchArgs, MemoryDeleteArgs, MemoryDeleteProjectArgs, MemoryListArgs, @@ -1578,7 +1578,7 @@ fn sanitize_provider_summary(provider: &Value) -> Result { #[cfg(test)] mod tests { - use serde_json::{Value, json}; + use serde_json::{json, Value}; use super::{ build_history_prefix_segments, flatten_history_messages_json, diff --git a/crates/agent-gui/src/lib/chat/conversation/run/gatewayBridgeEvents.ts b/crates/agent-gui/src/lib/chat/conversation/run/gatewayBridgeEvents.ts index 4e2b6956..13f0b0b2 100644 --- a/crates/agent-gui/src/lib/chat/conversation/run/gatewayBridgeEvents.ts +++ b/crates/agent-gui/src/lib/chat/conversation/run/gatewayBridgeEvents.ts @@ -4,6 +4,8 @@ type QueueEventOptions = { allowAfterClose?: boolean; }; +type GatewayBridgeSendResult = Promise | void; + type GatewayBridgeEventControllerParams = { conversationId: string; requestId: string; @@ -13,12 +15,19 @@ type GatewayBridgeEventControllerParams = { requestId: string, event: Record, options?: { workerId?: string }, - ) => void; + ) => GatewayBridgeSendResult; resolveErrorConversationId?: () => string; }; export type GatewayBridgeEventController = { - queueEvent: (event: Record, options?: QueueEventOptions) => void; + queueEvent: ( + event: Record, + options?: QueueEventOptions, + ) => GatewayBridgeSendResult; + queueUserMessage: ( + message: string, + uploadedFiles?: readonly unknown[], + ) => GatewayBridgeSendResult; queueToken: (delta: string, extra?: Record) => void; queueTitle: (nextTitle: string, allowAfterClose?: boolean) => void; queueToolStatus: (status: string | null, isCompaction?: boolean) => void; @@ -39,7 +48,7 @@ export function createGatewayBridgeEventController( const queueEvent = (event: Record, options?: QueueEventOptions) => { if (!params.enabled) return; if (streamClosed && !options?.allowAfterClose) return; - params.sendEvent(params.requestId, event, { workerId: params.workerId }); + return params.sendEvent(params.requestId, event, { workerId: params.workerId }); }; const queueToolStatus = (status: string | null, isCompaction = false) => { @@ -57,6 +66,17 @@ export function createGatewayBridgeEventController( return { queueEvent, + queueUserMessage(message: string, uploadedFiles = []) { + if (!message.trim() && uploadedFiles.length === 0) return; + return queueEvent({ + type: "user_message", + message, + uploaded_files: uploadedFiles.map((file) => + file && typeof file === "object" ? { ...(file as Record) } : file, + ), + conversation_id: params.conversationId, + }); + }, queueToken(delta: string, extra?: Record) { if (delta.length === 0 && !extra) return; if (delta.length > 0) { diff --git a/crates/agent-gui/src/pages/ChatPage.tsx b/crates/agent-gui/src/pages/ChatPage.tsx index 4a321c5f..afbb9b1a 100644 --- a/crates/agent-gui/src/pages/ChatPage.tsx +++ b/crates/agent-gui/src/pages/ChatPage.tsx @@ -132,7 +132,9 @@ import { isAgentDevMode, isAgentExecutionMode, isRightDockSingletonTabOpen, + normalizeChatRuntimeControls, normalizeChatRuntimeControlsForProvider, + normalizeSystemToolSelection, openRightDockSingletonTab, removeRightDockProjectState, resolveWorkspaceProjects, @@ -206,6 +208,11 @@ import { useLiveTranscriptController, usePendingUploads, } from "./chat"; +import { + type GatewayChatClaimedRequest, + normalizeGatewayExecutionMode, + normalizeGatewayWorkdir, +} from "./chat/gateway/gatewayBridgeTypes"; import { appendQueuedChatTurn, buildQueuedChatTurnPreview, @@ -214,6 +221,8 @@ import { insertQueuedChatTurnAtSlot, moveQueuedChatTurn, promoteQueuedChatTurn, + type ChatQueueItemDetail, + type ChatQueueSnapshot, type QueuedChatTurn, type QueuedChatTurnEditSlot, queuedChatTurnHasContent, @@ -249,6 +258,14 @@ const WorkspaceSshTerminalOverlay = lazy(async () => { }; }); +function createLocalGatewayChatRunId(conversationId: string) { + const suffix = + typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" + ? crypto.randomUUID() + : `${Date.now()}-${Math.random().toString(36).slice(2)}`; + return `conversation-live-${conversationId}-${suffix}`; +} + type ChatPageProps = { settings: AppSettings; setSettings: (updater: (prev: AppSettings) => AppSettings) => void; @@ -1805,16 +1822,92 @@ export function ChatPage(props: ChatPageProps) { workdir: string; selectedSystemToolIds: SystemToolId[]; runtimeControls: ChatRuntimeControls; + gatewayRequest?: QueuedChatTurn["gatewayRequest"]; }) | null >(null); + const chatQueueRevisionRef = useRef(0); + const remoteQueuedChatTurnEditSlotsRef = useRef< + Map< + string, + { + item: QueuedChatTurn; + slot: QueuedChatTurnEditSlot; + revision: number; + } + > + >(new Map()); + const gatewayConversationActivityChainsRef = useRef(new Map>()); const previousRunningConversationIdsRef = useRef>(new Set()); + function buildChatQueueSnapshot( + conversationId: string, + queue: readonly QueuedChatTurn[] = queuedChatTurnsRef.current, + ): ChatQueueSnapshot { + const key = conversationId.trim(); + return { + conversationId: key, + revision: chatQueueRevisionRef.current, + items: queue + .filter((item) => item.conversationId === key) + .map((item) => ({ + id: item.id, + previewText: buildQueuedChatTurnPreview(item.draft), + fileCount: item.uploadedFiles.length, + createdAt: item.createdAt, + source: item.gatewayRequest ? "webui" : "gui", + editable: true, + })), + }; + } + + function buildChatQueueItemDetail(item: QueuedChatTurn): ChatQueueItemDetail { + const summary = { + id: item.id, + previewText: buildQueuedChatTurnPreview(item.draft), + fileCount: item.uploadedFiles.length, + createdAt: item.createdAt, + source: item.gatewayRequest ? ("webui" as const) : ("gui" as const), + editable: true, + }; + return { + ...summary, + draftJson: JSON.stringify(item.draft), + uploadedFilesJson: JSON.stringify(item.uploadedFiles), + }; + } + + function publishChatQueueSnapshot( + conversationId: string, + queue: readonly QueuedChatTurn[] = queuedChatTurnsRef.current, + ) { + const snapshot = buildChatQueueSnapshot(conversationId, queue); + void invoke("gateway_publish_chat_queue_event", { + input: { + conversationId: snapshot.conversationId, + snapshotJson: JSON.stringify(snapshot), + revision: snapshot.revision, + }, + } as any).catch((error) => { + console.warn("gateway_publish_chat_queue_event failed", error); + }); + } + const setQueuedChatTurnsState = useCallback( (updater: (current: QueuedChatTurn[]) => QueuedChatTurn[]) => { - const next = updater(queuedChatTurnsRef.current).slice(); + const previous = queuedChatTurnsRef.current; + const next = updater(previous).slice(); queuedChatTurnsRef.current = next; setQueuedChatTurns(next); + chatQueueRevisionRef.current += 1; + const conversationIds = new Set(); + for (const item of previous) conversationIds.add(item.conversationId); + for (const item of next) conversationIds.add(item.conversationId); + const currentId = currentConversationIdRef.current.trim(); + if (currentId) conversationIds.add(currentId); + for (const conversationId of conversationIds) { + if (conversationId.trim()) publishChatQueueSnapshot(conversationId, next); + } return next; }, [], @@ -2110,6 +2203,7 @@ export function ChatPage(props: ChatPageProps) { selectedSystemToolIds: editSlot?.selectedSystemToolIds ?? settings.system.selectedSystemTools, runtimeControls: editSlot?.runtimeControls ?? settings.chatRuntimeControls, createdAt: editSlot?.createdAt, + gatewayRequest: editSlot?.gatewayRequest, }); setQueuedChatTurnsState((current) => { @@ -2153,8 +2247,35 @@ export function ChatPage(props: ChatPageProps) { if (!taken.item) return false; const queuedTurn = taken.item; inFlightQueuedTurn = queuedTurn; - queuedChatTurnsRef.current = taken.queue; - setQueuedChatTurns(taken.queue); + setQueuedChatTurnsState(() => taken.queue); + const gatewayRequest = queuedTurn.gatewayRequest; + const gatewayWorkerId = gatewayRequest?.workerId?.trim() || "gui-queue"; + const gatewayBridgeRequest: ActiveGatewayBridgeRequest | null = gatewayRequest + ? { + requestId: gatewayRequest.requestId, + conversationId: targetConversationId, + clientRequestId: gatewayRequest.clientRequestId, + workerId: gatewayWorkerId, + startedAt: Date.now(), + selectedModelOverride: gatewayRequest.selectedModel, + runtimeControlsOverride: gatewayRequest.runtimeControls + ? normalizeChatRuntimeControls(gatewayRequest.runtimeControls) + : queuedTurn.runtimeControls, + executionModeOverride: queuedTurn.executionMode, + workdirOverride: queuedTurn.workdir, + selectedSystemToolIdsOverride: queuedTurn.selectedSystemToolIds, + } + : null; + const markGatewayStarted = + gatewayRequest && gatewayBridgeRequest + ? async () => { + await invoke("gateway_chat_mark_started", { + request_id: gatewayRequest.requestId, + conversation_id: targetConversationId, + worker_id: gatewayWorkerId, + } as any); + } + : undefined; const accepted = await sendActionRef.current({ composerDraftOverride: queuedTurn.draft, uploadedFilesOverride: queuedTurn.uploadedFiles, @@ -2163,13 +2284,24 @@ export function ChatPage(props: ChatPageProps) { workdirOverride: queuedTurn.workdir, selectedSystemToolIdsOverride: queuedTurn.selectedSystemToolIds, runtimeControlsOverride: queuedTurn.runtimeControls, + gatewayBridgeRequestOverride: gatewayBridgeRequest, preserveComposerOnStart: true, + beforeRuntimeStart: markGatewayStarted, + afterInitialHistoryPersist: markGatewayStarted, }); if (!accepted) { setQueuedChatTurnsState((current) => promoteQueuedChatTurn(appendQueuedChatTurn(current, queuedTurn), queuedTurn.id), ); inFlightQueuedTurn = null; + } else if (gatewayRequest) { + void invoke("gateway_chat_complete", { + request_id: gatewayRequest.requestId, + conversation_id: targetConversationId, + worker_id: gatewayWorkerId, + } as any).catch((error) => { + console.warn("gateway_chat_complete failed", error); + }); } return accepted; }) @@ -2265,6 +2397,7 @@ export function ChatPage(props: ChatPageProps) { workdir: queuedTurn.workdir, selectedSystemToolIds: queuedTurn.selectedSystemToolIds.slice(), runtimeControls: { ...queuedTurn.runtimeControls }, + gatewayRequest: queuedTurn.gatewayRequest ? { ...queuedTurn.gatewayRequest } : undefined, }; setQueuedChatTurnsState((current) => removeQueuedChatTurn(current, key)); composerRef.current?.setDraft(queuedTurn.draft); @@ -2282,9 +2415,332 @@ export function ChatPage(props: ChatPageProps) { } function removeQueuedTurn(id: string) { + const queuedTurn = queuedChatTurnsRef.current.find((item) => item.id === id.trim()); setQueuedChatTurnsState((current) => removeQueuedChatTurn(current, id)); + const gatewayRequest = queuedTurn?.gatewayRequest; + if (gatewayRequest) { + void invoke("gateway_chat_cancel_request", { + request_id: gatewayRequest.requestId, + conversation_id: queuedTurn?.conversationId, + worker_id: gatewayRequest.workerId ?? "gui-queue", + } as any).catch((error) => { + console.warn("gateway_chat_cancel_request failed", error); + }); + } + } + + function createTextComposerDraft(text: string): MentionComposerDraft { + return { + segments: text ? [{ type: "text", text }] : [], + text, + textWithoutLargePastes: text, + largePastes: [], + skillMentions: [], + commitMentions: [], + gitFileMentions: [], + isEmpty: text.trim().length === 0, + }; } + function shouldQueueGatewayChatRequest( + conversationId: string, + queuePolicy: "auto" | "append" | "interrupt", + ) { + const key = conversationId.trim(); + if (!key) return false; + return ( + queuePolicy === "append" || + queuePolicy === "interrupt" || + queuedChatTurnsRef.current.some((item) => item.conversationId === key) || + isQueuedChatTurnEditBlockingProcessing(key) + ); + } + + async function enqueueGatewayChatRequest( + claimed: GatewayChatClaimedRequest, + conversationId: string, + ) { + const payload = claimed.request; + const requestId = payload.requestId.trim(); + const targetConversationId = conversationId.trim(); + const message = payload.message ?? ""; + const uploadedFiles = Array.isArray(payload.uploadedFiles) ? payload.uploadedFiles : []; + if (!requestId || !targetConversationId || (!message.trim() && uploadedFiles.length === 0)) { + return false; + } + + const executionMode = normalizeGatewayExecutionMode(payload.executionMode) ?? settings.system.executionMode; + const workdir = + normalizeGatewayWorkdir(payload.workdir) ?? + conversationRuntimeCacheRef.current.get(targetConversationId)?.workdir ?? + displayedConversationWorkdir ?? + settings.system.workdir; + const runtimeControls = payload.runtimeControls + ? normalizeChatRuntimeControls(payload.runtimeControls) + : settings.chatRuntimeControls; + const selectedSystemToolIds = normalizeSystemToolSelection(payload.selectedSystemTools); + const queuedTurn = createQueuedChatTurn({ + id: `gateway-${requestId}`, + conversationId: targetConversationId, + draft: createTextComposerDraft(message), + uploadedFiles, + executionMode, + workdir: isAgentExecutionMode(executionMode) ? workdir : "", + selectedSystemToolIds: + selectedSystemToolIds.length > 0 ? selectedSystemToolIds : settings.system.selectedSystemTools, + runtimeControls, + gatewayRequest: { + requestId, + clientRequestId: payload.clientRequestId?.trim() || claimed.clientRequestId?.trim() || undefined, + workerId: "gui-queue", + queuePolicy: + payload.queuePolicy === "append" || payload.queuePolicy === "interrupt" + ? payload.queuePolicy + : "auto", + selectedModel: payload.selectedModel, + runtimeControls: payload.runtimeControls, + }, + }); + + setQueuedChatTurnsState((current) => { + const appended = appendQueuedChatTurn(current, queuedTurn); + return payload.queuePolicy === "interrupt" + ? promoteQueuedChatTurn(appended, queuedTurn.id) + : appended; + }); + if (payload.queuePolicy === "interrupt") { + stopConversation(targetConversationId); + } + return true; + } + + useEffect(() => { + let disposed = false; + let unlisten: (() => void) | null = null; + type GatewayChatQueueRequestEvent = { + requestId: string; + action: string; + conversationId?: string; + itemId?: string; + direction?: "up" | "down" | string; + revision?: number; + draftJson?: string; + uploadedFilesJson?: string; + }; + + const respond = (requestId: string, response: Record) => { + if (!requestId.trim()) return; + void invoke("gateway_chat_queue_respond", { + input: { + requestId, + accepted: response.accepted === true, + message: typeof response.message === "string" ? response.message : "", + snapshotJson: typeof response.snapshotJson === "string" ? response.snapshotJson : "", + itemJson: typeof response.itemJson === "string" ? response.itemJson : "", + errorCode: typeof response.errorCode === "string" ? response.errorCode : "", + revision: chatQueueRevisionRef.current, + }, + } as any).catch((error) => { + console.warn("gateway_chat_queue_respond failed", error); + }); + }; + + const snapshotJson = (conversationId: string) => + JSON.stringify(buildChatQueueSnapshot(conversationId)); + + void listen("gateway:chat-queue-request", (event) => { + if (disposed) return; + const request = event.payload; + const requestId = request.requestId?.trim() ?? ""; + const action = request.action?.trim() ?? ""; + const conversationId = + request.conversationId?.trim() || currentConversationIdRef.current.trim(); + const itemId = request.itemId?.trim() ?? ""; + + const fail = (message: string, errorCode = "invalid_request") => { + respond(requestId, { + accepted: false, + message, + errorCode, + snapshotJson: conversationId ? snapshotJson(conversationId) : "", + }); + }; + + if (!requestId) return; + if (!conversationId && action !== "get") { + fail("conversation_id is required"); + return; + } + + if (action === "get") { + respond(requestId, { + accepted: true, + snapshotJson: snapshotJson(conversationId), + }); + return; + } + + const item = queuedChatTurnsRef.current.find( + (candidate) => candidate.id === itemId && candidate.conversationId === conversationId, + ); + + if (action === "get_item") { + if (!item) { + fail("queued item not found", "not_found"); + return; + } + respond(requestId, { + accepted: true, + itemJson: JSON.stringify(buildChatQueueItemDetail(item)), + snapshotJson: snapshotJson(conversationId), + }); + return; + } + + if (action === "run_now") { + if (!item) { + fail("queued item not found", "not_found"); + return; + } + runQueuedTurnNow(item.id); + respond(requestId, { accepted: true, snapshotJson: snapshotJson(conversationId) }); + return; + } + + if (action === "move") { + if (!item) { + fail("queued item not found", "not_found"); + return; + } + const direction = request.direction === "down" ? "down" : "up"; + setQueuedChatTurnsState((current) => moveQueuedChatTurn(current, item.id, direction)); + respond(requestId, { accepted: true, snapshotJson: snapshotJson(conversationId) }); + return; + } + + if (action === "remove") { + if (!item) { + fail("queued item not found", "not_found"); + return; + } + removeQueuedTurn(item.id); + respond(requestId, { accepted: true, snapshotJson: snapshotJson(conversationId) }); + return; + } + + if (action === "edit_begin") { + if (!item) { + fail("queued item not found", "not_found"); + return; + } + const sameConversationQueue = queuedChatTurnsRef.current.filter( + (candidate) => candidate.conversationId === conversationId, + ); + const sameConversationIndex = sameConversationQueue.findIndex( + (candidate) => candidate.id === item.id, + ); + const slot: QueuedChatTurnEditSlot = { + conversationId, + previousId: + sameConversationIndex > 0 + ? (sameConversationQueue[sameConversationIndex - 1]?.id ?? null) + : null, + nextId: + sameConversationIndex >= 0 + ? (sameConversationQueue[sameConversationIndex + 1]?.id ?? null) + : null, + index: sameConversationIndex >= 0 ? sameConversationIndex : undefined, + }; + remoteQueuedChatTurnEditSlotsRef.current.set(item.id, { + item, + slot, + revision: chatQueueRevisionRef.current, + }); + const detail = buildChatQueueItemDetail(item); + setQueuedChatTurnsState((current) => removeQueuedChatTurn(current, item.id)); + respond(requestId, { + accepted: true, + itemJson: JSON.stringify(detail), + snapshotJson: snapshotJson(conversationId), + }); + return; + } + + if (action === "edit_cancel") { + const session = remoteQueuedChatTurnEditSlotsRef.current.get(itemId); + if (!session) { + fail("queued edit session not found", "not_found"); + return; + } + if (session.slot.conversationId !== conversationId) { + fail("queued edit session conversation mismatch", "not_found"); + return; + } + remoteQueuedChatTurnEditSlotsRef.current.delete(itemId); + setQueuedChatTurnsState((current) => + insertQueuedChatTurnAtSlot(current, session.item, session.slot), + ); + respond(requestId, { accepted: true, snapshotJson: snapshotJson(conversationId) }); + return; + } + + if (action === "edit_commit") { + const session = remoteQueuedChatTurnEditSlotsRef.current.get(itemId); + if (!session) { + fail("queued edit session not found", "not_found"); + return; + } + if (session.slot.conversationId !== conversationId) { + fail("queued edit session conversation mismatch", "not_found"); + return; + } + if ( + typeof request.revision === "number" && + request.revision > 0 && + request.revision < chatQueueRevisionRef.current + ) { + fail("queued edit revision conflict", "conflict"); + return; + } + let draft: MentionComposerDraft; + let uploadedFiles: PendingUploadedFile[]; + try { + draft = JSON.parse(request.draftJson || "") as MentionComposerDraft; + uploadedFiles = JSON.parse(request.uploadedFilesJson || "[]") as PendingUploadedFile[]; + } catch { + fail("invalid queued edit payload", "invalid_payload"); + return; + } + const nextItem = createQueuedChatTurn({ + ...session.item, + draft, + uploadedFiles: Array.isArray(uploadedFiles) ? uploadedFiles : [], + id: session.item.id, + createdAt: session.item.createdAt, + }); + remoteQueuedChatTurnEditSlotsRef.current.delete(itemId); + setQueuedChatTurnsState((current) => + insertQueuedChatTurnAtSlot(current, nextItem, session.slot), + ); + respond(requestId, { accepted: true, snapshotJson: snapshotJson(conversationId) }); + return; + } + + fail(`unsupported chat queue action: ${action}`, "unsupported_action"); + }).then((dispose) => { + if (disposed) { + dispose(); + return; + } + unlisten = dispose; + }); + + return () => { + disposed = true; + unlisten?.(); + }; + }, []); + const { startNewConversation, loadConversationFromHistory, @@ -2622,6 +3078,30 @@ export function ChatPage(props: ChatPageProps) { } } + function queueGatewayConversationActivity( + conversationId: string, + running: boolean, + workdir?: string, + ) { + const targetConversationId = conversationId.trim(); + if (!targetConversationId) { + return; + } + + const previous = + gatewayConversationActivityChainsRef.current.get(targetConversationId) ?? + Promise.resolve(); + const next = previous + .catch(() => undefined) + .then(() => publishGatewayConversationActivity(targetConversationId, running, workdir)); + gatewayConversationActivityChainsRef.current.set(targetConversationId, next); + void next.finally(() => { + if (gatewayConversationActivityChainsRef.current.get(targetConversationId) === next) { + gatewayConversationActivityChainsRef.current.delete(targetConversationId); + } + }); + } + function applyGatewayBridgeRebase(conversationId: string, baseMessageRef: HistoryMessageRef) { const targetConversationId = conversationId.trim(); if (!targetConversationId) { @@ -2694,6 +3174,21 @@ export function ChatPage(props: ChatPageProps) { } const cached = conversationRuntimeCacheRef.current.get(requestedConversationId); + if ( + rebased && + baseMessageRef && + (cached || requestedConversationId === currentConversationIdRef.current) && + cached?.isSending !== true && + hydratingConversationIdRef.current !== requestedConversationId && + hydrationFailedConversationIdRef.current !== requestedConversationId + ) { + try { + applyGatewayBridgeRebase(requestedConversationId, baseMessageRef); + return requestedConversationId; + } catch (error) { + console.warn("gateway edit_resend cached rebase failed; hydrating history", error); + } + } if (rebased) { persistedConversationStateRef.current.delete(requestedConversationId); } @@ -2946,6 +3441,8 @@ export function ChatPage(props: ChatPageProps) { ensureGatewayBridgeConversationReadyRef, sendActionRef, queueGatewayBridgeEventForRequest, + shouldQueueGatewayChatRequest, + enqueueGatewayChatRequest, isConversationRunning, getConversationAbortController, }); @@ -3015,10 +3512,15 @@ export function ChatPage(props: ChatPageProps) { settings.remote.enabled && settings.remote.gatewayUrl.trim() !== "" && settings.remote.token.trim() !== ""; + const mirrorsLocalRunToGateway = !gatewayBridgeRequest && hasRemoteGatewayTarget; + const gatewayBridgeRequestId = + gatewayBridgeRequest?.requestId ?? createLocalGatewayChatRunId(conversationId); + const gatewayBridgeWorkerId = + gatewayBridgeRequest?.workerId ?? (mirrorsLocalRunToGateway ? "gui-live" : undefined); const gatewayBridgeEvents = createGatewayBridgeEventController({ conversationId, - requestId: gatewayBridgeRequest?.requestId ?? `conversation-live-${conversationId}`, - workerId: gatewayBridgeRequest?.workerId, + requestId: gatewayBridgeRequestId, + workerId: gatewayBridgeWorkerId, enabled: Boolean(gatewayBridgeRequest) || hasRemoteGatewayTarget, sendEvent: queueGatewayBridgeEventForRequest, resolveErrorConversationId: () => @@ -3289,20 +3791,12 @@ export function ChatPage(props: ChatPageProps) { ]); let conversationRunStarted = false; let gatewayRunStarted = false; - let gatewayActivityPublishChain: Promise = Promise.resolve(); - function queueGatewayConversationActivity(running: boolean) { - gatewayActivityPublishChain = gatewayActivityPublishChain.then(() => - publishGatewayConversationActivity(conversationId, running, conversationCwd), - ); - void gatewayActivityPublishChain; - } function acknowledgeGatewayRunStarted() { if (gatewayRunStarted) { return; } gatewayRunStarted = true; - gatewayBridgeEvents.queueToken("", { round: 0 }); - queueGatewayConversationActivity(true); + queueGatewayConversationActivity(conversationId, true, conversationCwd); } function markConversationRunStarted() { if (conversationRunStarted) { @@ -3324,12 +3818,47 @@ export function ChatPage(props: ChatPageProps) { setConversationAbortController(conversationId, null); setConversationSendingState(conversationId, false); if (gatewayRunStarted) { - queueGatewayConversationActivity(false); + queueGatewayConversationActivity(conversationId, false, conversationCwd); + } + } + let localGatewayRunStarted = false; + async function markLocalGatewayRunStarted() { + if (!mirrorsLocalRunToGateway || localGatewayRunStarted) { + return; + } + await invoke("gateway_chat_mark_local_started", { + request_id: gatewayBridgeRequestId, + conversation_id: conversationId, + } as any); + localGatewayRunStarted = true; + } + + markConversationRunStarted(); + if (mirrorsLocalRunToGateway) { + try { + await markLocalGatewayRunStarted(); + } catch (error) { + console.warn("gateway_chat_mark_local_started failed", error); + } + } + if (overrides?.beforeRuntimeStart) { + try { + await overrides.beforeRuntimeStart(); + } catch (error) { + const message = asErrorMessage(error, "启动远程对话运行失败"); + setConversationErrorState(message); + gatewayBridgeEvents.emitError(message, conversationId); + gatewayBridgeEvents.close(); + markConversationRunStopped(); + return false; } } // Persist the user turn immediately so WebUI/GUI sidebars can surface the - // latest conversation before the assistant round finishes. + // latest conversation before the assistant round finishes. For gateway + // requests, the desktop-owned run must be marked started first; otherwise + // the history running broadcast can make WebUI subscribe to the synthetic + // conversation-live run instead of the real queued request. const initialPersist = persistConversationWithHistorySync({ conversationId, sessionId, @@ -3342,7 +3871,6 @@ export function ChatPage(props: ChatPageProps) { titlePromise, titleLookahead: true, }); - markConversationRunStarted(); if (overrides?.afterInitialHistoryPersist && !overrides.beforeRuntimeStart) { const persisted = await initialPersist; if (!persisted) { @@ -3381,20 +3909,18 @@ export function ChatPage(props: ChatPageProps) { console.warn("initial conversation history persist confirmation failed", error); return false; }); - if (overrides?.beforeRuntimeStart) { - try { - await overrides.beforeRuntimeStart(); - } catch (error) { - const message = asErrorMessage(error, "启动远程对话运行失败"); - setConversationErrorState(message); - gatewayBridgeEvents.emitError(message, conversationId); - gatewayBridgeEvents.close(); - markConversationRunStopped(); - return true; - } - } void initialPersistConfirmation; } + if (gatewayBridgeRequest || hasRemoteGatewayTarget) { + const persisted = await initialPersist.catch((error) => { + console.warn("initial conversation history persist before gateway stream failed", error); + return false; + }); + if (!persisted) { + console.warn("gateway stream started before initial user turn was persisted"); + } + } + await gatewayBridgeEvents.queueUserMessage(text, uploadedFiles); acknowledgeGatewayRunStarted(); let activeCompactionRollback: { state: ConversationViewState; diff --git a/crates/agent-gui/src/pages/chat/gateway/gatewayBridgeTypes.ts b/crates/agent-gui/src/pages/chat/gateway/gatewayBridgeTypes.ts index 10731c59..1983c6ec 100644 --- a/crates/agent-gui/src/pages/chat/gateway/gatewayBridgeTypes.ts +++ b/crates/agent-gui/src/pages/chat/gateway/gatewayBridgeTypes.ts @@ -35,6 +35,7 @@ export type GatewayChatRequestEvent = { workdir?: string; selectedSystemTools?: string[]; uploadedFiles?: PendingUploadedFile[]; + queuePolicy?: "auto" | "append" | "interrupt" | string; }; export type GatewayChatClaimedRequest = { diff --git a/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeBatcher.ts b/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeBatcher.ts index 0f8d95eb..96732ced 100644 --- a/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeBatcher.ts +++ b/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeBatcher.ts @@ -331,8 +331,7 @@ export function useGatewayBridgeBatcher() { if (!batchable) { flushGatewayBridgeEventBatchesForRequest(requestId); discardDeferredToolCallDeltasForRequest(requestId); - sendGatewayBridgeEventForRequest(requestId, event, options); - return; + return sendGatewayBridgeEventForRequest(requestId, event, options); } const workerId = options?.workerId?.trim() || undefined; diff --git a/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeListeners.ts b/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeListeners.ts index 4787984b..af29dfe8 100644 --- a/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeListeners.ts +++ b/crates/agent-gui/src/pages/chat/gateway/useGatewayBridgeListeners.ts @@ -3,7 +3,10 @@ import { listen } from "@tauri-apps/api/event"; import { useEffect } from "react"; import type { HistoryMessageRef } from "../../../lib/chat/conversation/conversationState"; -import { normalizeChatRuntimeControls, normalizeSystemToolSelection } from "../../../lib/settings"; +import { + normalizeChatRuntimeControls, + normalizeSystemToolSelection, +} from "../../../lib/settings"; import { type ActiveGatewayBridgeRequest, type GatewayBridgeRuntimeRefs, @@ -19,9 +22,19 @@ type UseGatewayBridgeListenersParams = GatewayBridgeRuntimeRefs & { requestId: string, event: Record, options?: { workerId?: string }, - ) => void; + ) => Promise | void; + shouldQueueGatewayChatRequest: ( + conversationId: string, + queuePolicy: "auto" | "append" | "interrupt", + ) => boolean; + enqueueGatewayChatRequest: ( + claimed: GatewayChatClaimedRequest, + conversationId: string, + ) => Promise; isConversationRunning: (conversationId: string) => boolean; - getConversationAbortController: (conversationId: string) => AbortController | null; + getConversationAbortController: ( + conversationId: string, + ) => AbortController | null; }; type GatewayBridgeRequestRegistry = { @@ -54,7 +67,8 @@ const gatewayBridgeRequestRegistry = (() => { pendingClientRequestIds: new Set(), pendingConversationIds: new Set(), }; - root.__LIVEAGENT_GATEWAY_BRIDGE_REQUESTS__.pendingConversationIds ??= new Set(); + root.__LIVEAGENT_GATEWAY_BRIDGE_REQUESTS__.pendingConversationIds ??= + new Set(); return root.__LIVEAGENT_GATEWAY_BRIDGE_REQUESTS__; })(); @@ -72,7 +86,22 @@ function isConversationAlreadyRunningError(message: string) { return message.trim().startsWith("Conversation is already running:"); } -function normalizeGatewayBaseMessageRef(value: unknown): HistoryMessageRef | undefined { +function normalizeQueuePolicy( + value: string | null | undefined, +): "auto" | "append" | "interrupt" { + switch (value?.trim()) { + case "append": + return "append"; + case "interrupt": + return "interrupt"; + default: + return "auto"; + } +} + +function normalizeGatewayBaseMessageRef( + value: unknown, +): HistoryMessageRef | undefined { if (!value || typeof value !== "object") { return undefined; } @@ -85,18 +114,24 @@ function normalizeGatewayBaseMessageRef(value: unknown): HistoryMessageRef | und contentHash?: unknown; }; const segmentIndex = - typeof candidate.segmentIndex === "number" && Number.isFinite(candidate.segmentIndex) + typeof candidate.segmentIndex === "number" && + Number.isFinite(candidate.segmentIndex) ? Math.trunc(candidate.segmentIndex) : -1; const messageIndex = - typeof candidate.messageIndex === "number" && Number.isFinite(candidate.messageIndex) + typeof candidate.messageIndex === "number" && + Number.isFinite(candidate.messageIndex) ? Math.trunc(candidate.messageIndex) : -1; - const segmentId = typeof candidate.segmentId === "string" ? candidate.segmentId.trim() : ""; - const messageId = typeof candidate.messageId === "string" ? candidate.messageId.trim() : ""; + const segmentId = + typeof candidate.segmentId === "string" ? candidate.segmentId.trim() : ""; + const messageId = + typeof candidate.messageId === "string" ? candidate.messageId.trim() : ""; const role = typeof candidate.role === "string" ? candidate.role.trim() : ""; const contentHash = - typeof candidate.contentHash === "string" ? candidate.contentHash.trim() : ""; + typeof candidate.contentHash === "string" + ? candidate.contentHash.trim() + : ""; if ( segmentIndex < 0 || messageIndex < 0 || @@ -107,10 +142,19 @@ function normalizeGatewayBaseMessageRef(value: unknown): HistoryMessageRef | und ) { return undefined; } - return { segmentIndex, messageIndex, segmentId, messageId, role, contentHash }; + return { + segmentIndex, + messageIndex, + segmentId, + messageId, + role, + contentHash, + }; } -export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParams) { +export function useGatewayBridgeListeners( + params: UseGatewayBridgeListenersParams, +) { const { currentConversationIdRef, conversationRuntimeCacheRef, @@ -118,6 +162,8 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam ensureGatewayBridgeConversationReadyRef, sendActionRef, queueGatewayBridgeEventForRequest, + shouldQueueGatewayChatRequest, + enqueueGatewayChatRequest, isConversationRunning, getConversationAbortController, } = params; @@ -139,9 +185,13 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam gatewayBridgeRequestRegistry.pendingRequestIds.size; const runtimeVisible = () => - typeof document === "undefined" ? true : document.visibilityState !== "hidden"; + typeof document === "undefined" + ? true + : document.visibilityState !== "hidden"; - const publishRuntimeHeartbeat = (state?: "ready" | "draining" | "busy" | "suspended") => { + const publishRuntimeHeartbeat = ( + state?: "ready" | "draining" | "busy" | "suspended", + ) => { const activeRunCount = activeRuntimeRequestCount(); const nextState = state ?? (activeRunCount > 0 ? "busy" : "ready"); void invoke("gateway_chat_runtime_heartbeat", { @@ -154,13 +204,22 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam }); }; - const setActiveGatewayBridgeRequest = (request: ActiveGatewayBridgeRequest) => { + const setActiveGatewayBridgeRequest = ( + request: ActiveGatewayBridgeRequest, + ) => { gatewayBridgeRequestRegistry.pendingRequestIds.delete(request.requestId); if (request.clientRequestId) { - gatewayBridgeRequestRegistry.pendingClientRequestIds.delete(request.clientRequestId); + gatewayBridgeRequestRegistry.pendingClientRequestIds.delete( + request.clientRequestId, + ); } - gatewayBridgeRequestRegistry.pendingConversationIds.delete(request.conversationId); - gatewayBridgeRequestRegistry.activeRequests.set(request.requestId, request); + gatewayBridgeRequestRegistry.pendingConversationIds.delete( + request.conversationId, + ); + gatewayBridgeRequestRegistry.activeRequests.set( + request.requestId, + request, + ); publishRuntimeHeartbeat("busy"); return request; }; @@ -171,10 +230,15 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam }; const getActiveGatewayBridgeRequestByRequestId = (requestId: string) => { - return gatewayBridgeRequestRegistry.activeRequests.get(requestId.trim()) ?? null; + return ( + gatewayBridgeRequestRegistry.activeRequests.get(requestId.trim()) ?? + null + ); }; - const getActiveGatewayBridgeRequestByConversationId = (conversationId: string) => { + const getActiveGatewayBridgeRequestByConversationId = ( + conversationId: string, + ) => { const targetConversationId = conversationId.trim(); if (!targetConversationId) { return null; @@ -188,7 +252,9 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam return null; }; - const getActiveGatewayBridgeRequestByClientRequestId = (clientRequestId: string) => { + const getActiveGatewayBridgeRequestByClientRequestId = ( + clientRequestId: string, + ) => { const targetClientRequestId = clientRequestId.trim(); if (!targetClientRequestId) { return null; @@ -216,24 +282,32 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam } if ( clientRequestId && - (gatewayBridgeRequestRegistry.pendingClientRequestIds.has(clientRequestId) || + (gatewayBridgeRequestRegistry.pendingClientRequestIds.has( + clientRequestId, + ) || getActiveGatewayBridgeRequestByClientRequestId(clientRequestId)) ) { return "duplicate_client_request"; } if ( targetConversationId && - (gatewayBridgeRequestRegistry.pendingConversationIds.has(targetConversationId) || + (gatewayBridgeRequestRegistry.pendingConversationIds.has( + targetConversationId, + ) || getActiveGatewayBridgeRequestByConversationId(targetConversationId)) ) { return "conversation_busy"; } gatewayBridgeRequestRegistry.pendingRequestIds.add(requestId); if (clientRequestId) { - gatewayBridgeRequestRegistry.pendingClientRequestIds.add(clientRequestId); + gatewayBridgeRequestRegistry.pendingClientRequestIds.add( + clientRequestId, + ); } if (targetConversationId) { - gatewayBridgeRequestRegistry.pendingConversationIds.add(targetConversationId); + gatewayBridgeRequestRegistry.pendingConversationIds.add( + targetConversationId, + ); } publishRuntimeHeartbeat("busy"); return "claimed"; @@ -247,10 +321,14 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam ) => { gatewayBridgeRequestRegistry.pendingRequestIds.delete(requestId); if (clientRequestId) { - gatewayBridgeRequestRegistry.pendingClientRequestIds.delete(clientRequestId); + gatewayBridgeRequestRegistry.pendingClientRequestIds.delete( + clientRequestId, + ); } if (conversationId) { - gatewayBridgeRequestRegistry.pendingConversationIds.delete(conversationId); + gatewayBridgeRequestRegistry.pendingConversationIds.delete( + conversationId, + ); } if (request) { clearActiveGatewayBridgeRequest(request.requestId); @@ -306,13 +384,35 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam }); }; - const handleGatewayChatRequest = async (claimed: GatewayChatClaimedRequest) => { + const markQueuedInGui = async ( + claimed: GatewayChatClaimedRequest, + conversationId: string, + ) => { + const requestId = claimed.requestId.trim(); + if (!requestId) return false; + const queued = await enqueueGatewayChatRequest(claimed, conversationId); + if (!queued) return false; + await invoke("gateway_chat_mark_queued_in_gui", { + request_id: requestId, + conversation_id: conversationId, + worker_id: workerId, + } as any); + stopHeartbeat(requestId); + return true; + }; + + const handleGatewayChatRequest = async ( + claimed: GatewayChatClaimedRequest, + ) => { const payload = claimed.request; const requestId = payload.requestId.trim(); const clientRequestId = payload.clientRequestId?.trim() ?? ""; const message = payload.message.trim(); - const uploadedFiles = Array.isArray(payload.uploadedFiles) ? payload.uploadedFiles : []; + const uploadedFiles = Array.isArray(payload.uploadedFiles) + ? payload.uploadedFiles + : []; const targetConversationId = payload.conversationId.trim(); + const queuePolicy = normalizeQueuePolicy(payload.queuePolicy); let resolvedConversationId = targetConversationId; let gatewayBridgeRequest: ActiveGatewayBridgeRequest | null = null; let claimedRequest = false; @@ -349,25 +449,15 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam ); if (claimResult !== "claimed") { if (claimResult === "conversation_busy") { - queueGatewayBridgeEventForRequest( - requestId, - { - type: "error", - message: GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE, - conversation_id: targetConversationId, - }, - { - workerId, - }, - ); - failClaimedRequest( - requestId, - targetConversationId, - "conversation_busy", - GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE, - ); - stopHeartbeat(requestId); - return; + if (targetConversationId) { + try { + if (await markQueuedInGui(claimed, targetConversationId)) { + return; + } + } catch (error) { + console.warn("queue remote gateway chat request failed", error); + } + } } void invoke("gateway_chat_release_lease", { request_id: requestId, @@ -395,16 +485,18 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam }); return; } - if ( - targetConversationId && - (isConversationRunning(targetConversationId) || - getConversationAbortController(targetConversationId)) - ) { + const baseMessageRef = + payload.rebased === true + ? normalizeGatewayBaseMessageRef(payload.baseMessageRef) + : undefined; + if (payload.rebased === true && !baseMessageRef) { + const message = + "Remote edit_resend command is missing base_message_ref."; queueGatewayBridgeEventForRequest( requestId, { type: "error", - message: GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE, + message, conversation_id: targetConversationId, }, { @@ -414,68 +506,55 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam failClaimedRequest( requestId, targetConversationId, - "conversation_busy", - GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE, + "invalid_chat_command", + message, ); return; } - const baseMessageRef = - payload.rebased === true - ? normalizeGatewayBaseMessageRef(payload.baseMessageRef) - : undefined; - if (payload.rebased === true && !baseMessageRef) { - const message = "Remote edit_resend command is missing base_message_ref."; - queueGatewayBridgeEventForRequest( - requestId, - { - type: "error", - message, - conversation_id: targetConversationId, - }, - { - workerId, - }, - ); - failClaimedRequest(requestId, targetConversationId, "invalid_chat_command", message); + if ( + targetConversationId && + payload.rebased !== true && + (shouldQueueGatewayChatRequest(targetConversationId, queuePolicy) || + isConversationRunning(targetConversationId) || + getConversationAbortController(targetConversationId)) + ) { + if (await markQueuedInGui(claimed, targetConversationId)) { + return; + } return; } - resolvedConversationId = await ensureGatewayBridgeConversationReadyRef.current( - targetConversationId, - { - rebased: payload.rebased === true, - baseMessageRef, - }, - ); + resolvedConversationId = + await ensureGatewayBridgeConversationReadyRef.current( + targetConversationId, + { + rebased: payload.rebased === true, + baseMessageRef, + }, + ); const runningRequest = - getActiveGatewayBridgeRequestByConversationId(resolvedConversationId) || + getActiveGatewayBridgeRequestByConversationId( + resolvedConversationId, + ) || (clientRequestId ? getActiveGatewayBridgeRequestByClientRequestId(clientRequestId) : null); if ( + shouldQueueGatewayChatRequest(resolvedConversationId, queuePolicy) || runningRequest || isConversationRunning(resolvedConversationId) || getConversationAbortController(resolvedConversationId) ) { - queueGatewayBridgeEventForRequest( - requestId, - { - type: "error", - message: GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE, - conversation_id: runningRequest?.conversationId || resolvedConversationId, - }, - { - workerId, - }, - ); - failClaimedRequest( - requestId, - runningRequest?.conversationId || resolvedConversationId, - "conversation_busy", - GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE, - ); + if ( + await markQueuedInGui( + claimed, + runningRequest?.conversationId || resolvedConversationId, + ) + ) { + return; + } return; } @@ -489,9 +568,13 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam runtimeControlsOverride: payload.runtimeControls ? normalizeChatRuntimeControls(payload.runtimeControls) : undefined, - executionModeOverride: normalizeGatewayExecutionMode(payload.executionMode), + executionModeOverride: normalizeGatewayExecutionMode( + payload.executionMode, + ), workdirOverride: normalizeGatewayWorkdir(payload.workdir), - selectedSystemToolIdsOverride: normalizeSystemToolSelection(payload.selectedSystemTools), + selectedSystemToolIdsOverride: normalizeSystemToolSelection( + payload.selectedSystemTools, + ), }); const markRuntimeStarted = async () => { await invoke("gateway_chat_mark_started", { @@ -500,18 +583,28 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam worker_id: workerId, } as any); }; - await sendActionRef.current({ + const accepted = await sendActionRef.current({ textOverride: message, uploadedFilesOverride: uploadedFiles, conversationIdOverride: resolvedConversationId, executionModeOverride: gatewayBridgeRequest.executionModeOverride, workdirOverride: gatewayBridgeRequest.workdirOverride, - selectedSystemToolIdsOverride: gatewayBridgeRequest.selectedSystemToolIdsOverride, + selectedSystemToolIdsOverride: + gatewayBridgeRequest.selectedSystemToolIdsOverride, runtimeControlsOverride: gatewayBridgeRequest.runtimeControlsOverride, gatewayBridgeRequestOverride: gatewayBridgeRequest, beforeRuntimeStart: markRuntimeStarted, afterInitialHistoryPersist: markRuntimeStarted, }); + if (!accepted) { + failClaimedRequest( + requestId, + resolvedConversationId, + "desktop_runtime_rejected", + "Desktop app rejected the remote gateway chat request.", + ); + return; + } await invoke("gateway_chat_complete", { request_id: requestId, conversation_id: resolvedConversationId, @@ -523,14 +616,18 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam "Failed to execute the remote gateway chat request.", ); const conversationBusy = isConversationAlreadyRunningError(rawMessage); - const message = conversationBusy ? GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE : rawMessage; + const message = conversationBusy + ? GATEWAY_CHAT_CONVERSATION_BUSY_MESSAGE + : rawMessage; queueGatewayBridgeEventForRequest( requestId, { type: "error", message, conversation_id: - resolvedConversationId || targetConversationId || currentConversationIdRef.current, + resolvedConversationId || + targetConversationId || + currentConversationIdRef.current, }, { workerId, @@ -538,7 +635,9 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam ); failClaimedRequest( requestId, - resolvedConversationId || targetConversationId || currentConversationIdRef.current, + resolvedConversationId || + targetConversationId || + currentConversationIdRef.current, conversationBusy ? "conversation_busy" : "desktop_runtime_error", message, ); @@ -586,10 +685,13 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam } }; - void listen("gateway:chat-request-ready", () => { - publishRuntimeHeartbeat("draining"); - void drainGatewayChatInbox(); - }).then((dispose) => { + void listen( + "gateway:chat-request-ready", + () => { + publishRuntimeHeartbeat("draining"); + void drainGatewayChatInbox(); + }, + ).then((dispose) => { if (disposed) { dispose(); return; @@ -674,6 +776,8 @@ export function useGatewayBridgeListeners(params: UseGatewayBridgeListenersParam getConversationAbortController, historyItemsRef, isConversationRunning, + shouldQueueGatewayChatRequest, + enqueueGatewayChatRequest, queueGatewayBridgeEventForRequest, sendActionRef, ]); diff --git a/crates/agent-gui/src/pages/chat/queue/chatTurnQueue.ts b/crates/agent-gui/src/pages/chat/queue/chatTurnQueue.ts index 64d70a12..278447dc 100644 --- a/crates/agent-gui/src/pages/chat/queue/chatTurnQueue.ts +++ b/crates/agent-gui/src/pages/chat/queue/chatTurnQueue.ts @@ -1,6 +1,19 @@ import type { MentionComposerDraft } from "../../../components/chat/MentionComposer"; import type { PendingUploadedFile } from "../../../lib/chat/messages/uploadedFiles"; import type { ChatRuntimeControls, ExecutionMode, SystemToolId } from "../../../lib/settings"; +import type { + GatewaySelectedModelEvent, + GatewayChatRuntimeControlsEvent, +} from "../gateway/gatewayBridgeTypes"; + +export type QueuedGatewayChatRequest = { + requestId: string; + clientRequestId?: string; + workerId?: string; + queuePolicy?: "auto" | "append" | "interrupt"; + selectedModel?: GatewaySelectedModelEvent; + runtimeControls?: GatewayChatRuntimeControlsEvent; +}; export type QueuedChatTurn = { id: string; @@ -12,6 +25,27 @@ export type QueuedChatTurn = { selectedSystemToolIds: SystemToolId[]; runtimeControls: ChatRuntimeControls; createdAt: number; + gatewayRequest?: QueuedGatewayChatRequest; +}; + +export type ChatQueueItemSummary = { + id: string; + previewText: string; + fileCount: number; + createdAt: number; + source: "gui" | "webui"; + editable: boolean; +}; + +export type ChatQueueSnapshot = { + conversationId: string; + revision: number; + items: ChatQueueItemSummary[]; +}; + +export type ChatQueueItemDetail = ChatQueueItemSummary & { + draftJson: string; + uploadedFilesJson: string; }; export type QueuedChatTurnInput = Omit & { @@ -38,6 +72,7 @@ export function createQueuedChatTurn(input: QueuedChatTurnInput): QueuedChatTurn selectedSystemToolIds: input.selectedSystemToolIds.slice(), runtimeControls: { ...input.runtimeControls }, createdAt, + gatewayRequest: input.gatewayRequest ? { ...input.gatewayRequest } : undefined, }; } @@ -59,6 +94,8 @@ export function buildQueuedChatTurnPreview(draft: MentionComposerDraft) { return segment.commit.subject || segment.commit.shortSha || segment.commit.sha; case "gitFileMention": return segment.file.path; + case "fileMention": + return segment.reference.path; case "text": return segment.text; } diff --git a/crates/agent-gui/test/chat/conversation-state.test.mjs b/crates/agent-gui/test/chat/conversation-state.test.mjs index f5a13a29..7506001f 100644 --- a/crates/agent-gui/test/chat/conversation-state.test.mjs +++ b/crates/agent-gui/test/chat/conversation-state.test.mjs @@ -173,17 +173,18 @@ test("compaction checkpoint creates a summarized segment and carries summary int test("truncateConversationFromMessage removes later segments and rebuilds render timeline", () => { const state = conversationState.createConversationStateFromContext({ messages: [ - user("one", 1), - assistant("two", 2), - user("three", 3), - assistant("four", 4), + { ...user("one", 1), id: "message-1" }, + { ...assistant("two", 2), id: "message-2" }, + { ...user("three", 3), id: "message-3" }, + { ...assistant("four", 4), id: "message-4" }, ], }); - const truncated = conversationState.truncateConversationFromMessage(state, { - segmentIndex: 0, - messageIndex: 2, - }); + const target = state.historyRenderItems.find( + (item) => item.kind === "user" && item.text === "three", + ); + assert.ok(target?.messageRef); + const truncated = conversationState.truncateConversationFromMessage(state, target.messageRef); assert.equal(truncated.activeSegmentIndex, 0); assert.deepEqual( From 0a6fbcbe15b26db151d5cc6271ce1e0a3b9959a2 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Wed, 24 Jun 2026 17:00:59 +0800 Subject: [PATCH 7/8] fix(webui): stabilize queued uploads and edit resend --- .../test/webui/history-chat-ui.test.mjs | 73 +++++++++++++++++++ .../agent-gateway/web/src/app/GatewayApp.tsx | 36 +++++---- .../web/src/app/hooks/usePendingUploads.ts | 7 -- crates/agent-gateway/web/src/i18n/config.ts | 2 - .../web/src/lib/chat/uploadedFiles.ts | 31 ++++++++ crates/agent-gateway/web/src/lib/chatUi.ts | 22 +++++- .../web/src/lib/liveConversationCommit.ts | 10 ++- 7 files changed, 154 insertions(+), 27 deletions(-) diff --git a/crates/agent-gateway/test/webui/history-chat-ui.test.mjs b/crates/agent-gateway/test/webui/history-chat-ui.test.mjs index 64e71f70..bcef0dd7 100644 --- a/crates/agent-gateway/test/webui/history-chat-ui.test.mjs +++ b/crates/agent-gateway/test/webui/history-chat-ui.test.mjs @@ -1641,6 +1641,35 @@ test("omitEquivalentTailEntries removes live user and assistant overlap", () => ); }); +test("omitEquivalentTailEntries removes uploaded live user overlap", () => { + const historyAttachment = { + relativePath: ".liveagent/uploads/08-conclusion.png", + fileName: "08-conclusion.png", + kind: "image", + sizeBytes: 58368, + absolutePath: "/workspace/.liveagent/uploads/08-conclusion.png", + }; + const liveAttachment = { + relativePath: ".liveagent/uploads/08-conclusion.png", + absolutePath: "/workspace/.liveagent/uploads/08-conclusion.png", + fileName: "08-conclusion.png", + kind: "image", + sizeBytes: 58368, + }; + const existing = [ + { id: "history-user-1", kind: "user", text: "这是什么", attachments: [historyAttachment] }, + { id: "history-assistant-1", kind: "assistant", text: "reply", round: 1 }, + ]; + const liveEntries = [ + { id: "live-user-1", kind: "user", text: "这是什么", attachments: [liveAttachment] }, + { id: "live-assistant-1", kind: "assistant", text: "reply", round: 1 }, + ]; + + const visibleHistory = liveCommit.omitEquivalentTailEntries(existing, liveEntries); + + assert.deepEqual(visibleHistory, []); +}); + test("appendCommittedLiveEntries does not duplicate optimistic user message overlaps", () => { const existing = [ { id: "history-user-1", kind: "user", text: "first", attachments: [] }, @@ -1661,6 +1690,50 @@ test("appendCommittedLiveEntries does not duplicate optimistic user message over assert.equal(merged[2].kind, "assistant"); }); +test("appendCommittedLiveEntries does not duplicate uploaded optimistic user overlaps", () => { + const existing = [ + { + id: "optimistic-user-1", + kind: "user", + text: "这是什么", + attachments: [ + { + relativePath: ".liveagent/uploads/08-conclusion.png", + fileName: "08-conclusion.png", + kind: "image", + sizeBytes: 58368, + absolutePath: "/workspace/.liveagent/uploads/08-conclusion.png", + }, + ], + }, + ]; + const liveEntries = [ + { + id: "live-user-1", + kind: "user", + text: "这是什么", + attachments: [ + { + relativePath: ".liveagent/uploads/08-conclusion.png", + absolutePath: "/workspace/.liveagent/uploads/08-conclusion.png", + fileName: "08-conclusion.png", + kind: "image", + sizeBytes: 58368, + }, + ], + }, + { id: "live-assistant-1", kind: "assistant", text: "reply", round: 1 }, + ]; + + const merged = liveCommit.appendCommittedLiveEntries(existing, liveEntries); + + assert.deepEqual( + merged.map((entry) => entry.text), + ["这是什么", "reply"], + ); + assert.equal(merged[0].id, "optimistic-user-1"); +}); + test("mergeHistorySnapshotEntries replaces stale local entries when an authoritative snapshot is shorter", () => { // Simulates: peer A edits the user prompt and resends, server-side history // is now just the new user turn. Without `isFullSnapshot`, the local two-turn diff --git a/crates/agent-gateway/web/src/app/GatewayApp.tsx b/crates/agent-gateway/web/src/app/GatewayApp.tsx index 6dca1896..c729db37 100644 --- a/crates/agent-gateway/web/src/app/GatewayApp.tsx +++ b/crates/agent-gateway/web/src/app/GatewayApp.tsx @@ -466,7 +466,6 @@ export default function GatewayApp() { executionMode: settings.system.executionMode, conversationId, selectedHistoryId, - chatBusyRef, displayedConversationWorkdirRef, composerRef, setChatError, @@ -5056,7 +5055,7 @@ export default function GatewayApp() { messages: buildPreviewMessages(current.messages), })); - try { + const hydrateEditPrefix = async () => { const detail = await api.getHistoryPrefix(activeConversationId, messageRef, { maxMessages: HISTORY_DETAIL_INITIAL_MAX_MESSAGES, }); @@ -5067,18 +5066,21 @@ export default function GatewayApp() { if (!isCurrentEditTransaction()) { return; } - const nextRuntime = createConversationRuntimeEntry({ - messages: [...entries, optimisticEditUserEntry], - error: null, - toolStatus: null, - toolStatusIsCompaction: false, - isSending: false, - }); setSelectedHistory(detail); setSelectedHistoryEntries(entries); - conversationRuntimeCacheRef.current.set(activeConversationId, nextRuntime); - syncVisibleConversationRuntime(activeConversationId, nextRuntime); + updateConversationRuntimeEntry(activeConversationId, (current) => { + if ( + !current.isSending && + !localRunningConversationIdsRef.current.has(activeConversationId) + ) { + return current; + } + return { + ...current, + messages: [...entries, optimisticEditUserEntry], + }; + }); const truncatedConversation = detail.conversation; if (truncatedConversation) { @@ -5086,7 +5088,15 @@ export default function GatewayApp() { upsertConversationSummary(current, truncatedConversation), ); } + }; + void hydrateEditPrefix().catch((error) => { + if (isCurrentEditTransaction()) { + console.warn("edit resend history prefix hydration failed", error); + } + }); + + try { const resendPromise = sendChatRef.current?.(normalized, { conversationId: activeConversationId, @@ -5104,7 +5114,7 @@ export default function GatewayApp() { }); } } catch (error) { - const message = asErrorMessage(error, "回溯历史消息失败"); + const message = asErrorMessage(error, "编辑后重发失败"); setChatError(message); updateConversationRuntimeEntry(activeConversationId, (current) => ({ ...current, @@ -5131,7 +5141,6 @@ export default function GatewayApp() { invalidateHistoryLoad, markVisibleConversationRevision, refreshVisibleConversationHistorySnapshot, - syncVisibleConversationRuntime, updateHistoryItems, updateConversationRuntimeEntry, ], @@ -5739,7 +5748,6 @@ export default function GatewayApp() { status?.online === true && isAgentMode && Boolean(displayedConversationWorkdir.trim()) && - !composerIsSending && !isUploadingFiles && !composerInputDisabled; const fileDropTitle = canDropUpload diff --git a/crates/agent-gateway/web/src/app/hooks/usePendingUploads.ts b/crates/agent-gateway/web/src/app/hooks/usePendingUploads.ts index 2f923859..c969bbd1 100644 --- a/crates/agent-gateway/web/src/app/hooks/usePendingUploads.ts +++ b/crates/agent-gateway/web/src/app/hooks/usePendingUploads.ts @@ -28,7 +28,6 @@ type UsePendingUploadsParams = { executionMode: AppSettings["system"]["executionMode"]; conversationId: string; selectedHistoryId: string; - chatBusyRef: RefObject; displayedConversationWorkdirRef: RefObject; composerRef: RefObject; setChatError: (message: string | null) => void; @@ -45,7 +44,6 @@ export function usePendingUploads(params: UsePendingUploadsParams) { executionMode, conversationId, selectedHistoryId, - chatBusyRef, displayedConversationWorkdirRef, composerRef, setChatError, @@ -146,10 +144,6 @@ export function usePendingUploads(params: UsePendingUploadsParams) { if (filesToImport.length === 0) { return; } - if (chatBusyRef.current) { - setChatError(translate("chat.upload.busyGenerating", locale)); - return; - } if (isUploadingFilesRef.current) { setChatError(translate("chat.upload.uploading", locale)); return; @@ -231,7 +225,6 @@ export function usePendingUploads(params: UsePendingUploadsParams) { } }, [ - chatBusyRef, composerRef, displayedConversationWorkdirRef, executionMode, diff --git a/crates/agent-gateway/web/src/i18n/config.ts b/crates/agent-gateway/web/src/i18n/config.ts index 2f2b3fe2..2f3822f0 100644 --- a/crates/agent-gateway/web/src/i18n/config.ts +++ b/crates/agent-gateway/web/src/i18n/config.ts @@ -126,7 +126,6 @@ export const translations: Record> = { "chat.upload.button": "上传文件", "chat.upload.selectFiles": "选择并导入一个或多个当前对话可读取的文件", "chat.upload.uploading": "上传中...", - "chat.upload.busyGenerating": "正在生成回复,暂时无法上传文件", "chat.upload.onlyInTools": "文件上传仅在 tools 模式可用", "chat.upload.requireWorkdir": "请先选择一个项目", "chat.upload.removeFile": "移除文件", @@ -1585,7 +1584,6 @@ export const translations: Record> = { "chat.upload.button": "Upload Files", "chat.upload.selectFiles": "Select and import one or more readable files into the workspace", "chat.upload.uploading": "Uploading...", - "chat.upload.busyGenerating": "A response is still running, so files cannot be uploaded yet", "chat.upload.onlyInTools": "File upload is only available in tools mode", "chat.upload.requireWorkdir": "Select a project first", "chat.upload.removeFile": "Remove file", diff --git a/crates/agent-gateway/web/src/lib/chat/uploadedFiles.ts b/crates/agent-gateway/web/src/lib/chat/uploadedFiles.ts index 8143f10e..9482af55 100644 --- a/crates/agent-gateway/web/src/lib/chat/uploadedFiles.ts +++ b/crates/agent-gateway/web/src/lib/chat/uploadedFiles.ts @@ -223,6 +223,37 @@ export function getUserMessageAttachments( }); } +export function normalizeUploadedFileForDisplayComparison(file: PendingUploadedFile) { + return { + relativePath: file.relativePath, + absolutePath: file.absolutePath || "", + fileName: file.fileName, + kind: file.kind, + sizeBytes: Number.isFinite(file.sizeBytes) ? Math.max(0, Math.floor(file.sizeBytes)) : 0, + displayMode: file.displayMode || "", + displayLabel: file.displayLabel || "", + displayCharCount: + typeof file.displayCharCount === "number" && Number.isFinite(file.displayCharCount) + ? Math.max(0, Math.floor(file.displayCharCount)) + : 0, + displayLineCount: + typeof file.displayLineCount === "number" && Number.isFinite(file.displayLineCount) + ? Math.max(0, Math.floor(file.displayLineCount)) + : 0, + }; +} + +function uploadedFilesDisplayKey(files: readonly PendingUploadedFile[]) { + return JSON.stringify(files.map(normalizeUploadedFileForDisplayComparison)); +} + +export function uploadedFilesVisuallyEqual( + left: readonly PendingUploadedFile[], + right: readonly PendingUploadedFile[], +) { + return uploadedFilesDisplayKey(left) === uploadedFilesDisplayKey(right); +} + export function stripUploadedFilesMessageMetadata(message: Message): Message { if (message.role !== "user") return message; const userMessage = message as Message & Record; diff --git a/crates/agent-gateway/web/src/lib/chatUi.ts b/crates/agent-gateway/web/src/lib/chatUi.ts index 2ce80a36..403ab5d7 100644 --- a/crates/agent-gateway/web/src/lib/chatUi.ts +++ b/crates/agent-gateway/web/src/lib/chatUi.ts @@ -5,6 +5,7 @@ import { getUserMessageAttachments, getUserMessageDisplayText, type PendingUploadedFile, + uploadedFilesVisuallyEqual, } from "@/lib/chat/uploadedFiles"; import { @@ -246,13 +247,30 @@ function normalizeLiveUploadedFile(value: unknown): PendingUploadedFile | null { return null; } const absolutePath = readString(record.absolutePath ?? record.absolute_path).trim(); - return { + const file: PendingUploadedFile = { relativePath, absolutePath: absolutePath || undefined, fileName, kind: kind as PendingUploadedFile["kind"], sizeBytes: Math.max(0, Math.floor(sizeBytes)), }; + const displayMode = readString(record.displayMode ?? record.display_mode).trim(); + if (displayMode === "largePaste") { + file.displayMode = "largePaste"; + } + const displayLabel = readString(record.displayLabel ?? record.display_label).trim(); + if (displayLabel) { + file.displayLabel = displayLabel; + } + const displayCharCount = readNumber(record.displayCharCount ?? record.display_char_count); + if (typeof displayCharCount === "number") { + file.displayCharCount = Math.max(0, Math.floor(displayCharCount)); + } + const displayLineCount = readNumber(record.displayLineCount ?? record.display_line_count); + if (typeof displayLineCount === "number") { + file.displayLineCount = Math.max(0, Math.floor(displayLineCount)); + } + return file; } function normalizeLiveUploadedFiles(value: unknown): PendingUploadedFile[] { @@ -272,7 +290,7 @@ function liveUserEntryMatches( return ( entry?.kind === "user" && entry.text === text && - JSON.stringify(entry.attachments) === JSON.stringify(attachments) + uploadedFilesVisuallyEqual(entry.attachments, attachments) ); } diff --git a/crates/agent-gateway/web/src/lib/liveConversationCommit.ts b/crates/agent-gateway/web/src/lib/liveConversationCommit.ts index 1f84b0aa..e7357cee 100644 --- a/crates/agent-gateway/web/src/lib/liveConversationCommit.ts +++ b/crates/agent-gateway/web/src/lib/liveConversationCommit.ts @@ -1,4 +1,5 @@ import type { ChatEntry } from "./chatUi"; +import { normalizeUploadedFileForDisplayComparison } from "./chat/uploadedFiles"; function cloneValue(value: T): T { if (value == null) { @@ -42,8 +43,13 @@ function buildVisualComparableEntry(entry: ChatEntry) { // post-stream history refresh would replace every user entry with a fresh // id, all article keys would change, and the `chat-bubble-enter` // animation would re-run for every user bubble at once). - const { messageRef: _messageRef, ...rest } = comparable; - return rest; + const { messageRef: _messageRef, attachments, ...rest } = comparable; + return { + ...rest, + attachments: Array.isArray(attachments) + ? attachments.map(normalizeUploadedFileForDisplayComparison) + : [], + }; } return comparable; } From a1b28e736b84d35437ef2ee519e724abfbe8f01d Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Wed, 24 Jun 2026 17:22:31 +0800 Subject: [PATCH 8/8] fix(gui): mark live tool cards as running --- .../src/lib/chat/messages/uiMessages.ts | 34 +++++++++ .../chat/turns/runAgentConversationTurn.ts | 35 ++++----- crates/agent-gui/test/chat/messages.test.mjs | 73 +++++++++++++++++++ 3 files changed, 120 insertions(+), 22 deletions(-) diff --git a/crates/agent-gui/src/lib/chat/messages/uiMessages.ts b/crates/agent-gui/src/lib/chat/messages/uiMessages.ts index 3a3a9738..2a0d64e8 100644 --- a/crates/agent-gui/src/lib/chat/messages/uiMessages.ts +++ b/crates/agent-gui/src/lib/chat/messages/uiMessages.ts @@ -1422,6 +1422,40 @@ export function upsertToolCallToRound>( }; } +export function markToolCallRunningInRound< + TRound extends Pick & { runningToolCallIds: string[] }, +>(round: TRound, toolCall: ToolCall): TRound { + const visibleToolCalls = buildDelegateAgentPlaceholderToolCalls(toolCall); + const runningCandidateIds = + visibleToolCalls.length > 0 + ? visibleToolCalls.map((item) => item.id) + : toolCall.id + ? [toolCall.id] + : []; + if (runningCandidateIds.length === 0) return round; + + const visibleToolCallIds = new Set( + getRoundToolTrace(round) + .map((item) => item.toolCall.id) + .filter((id): id is string => Boolean(id)), + ); + let runningToolCallIds = round.runningToolCallIds; + for (const id of runningCandidateIds) { + if (!visibleToolCallIds.has(id) || runningToolCallIds.includes(id)) continue; + if (runningToolCallIds === round.runningToolCallIds) { + runningToolCallIds = runningToolCallIds.slice(); + } + runningToolCallIds.push(id); + } + + return runningToolCallIds === round.runningToolCallIds + ? round + : { + ...round, + runningToolCallIds, + }; +} + export function attachToolResultToRound>( round: TRound, toolCall: ToolCall, diff --git a/crates/agent-gui/src/pages/chat/turns/runAgentConversationTurn.ts b/crates/agent-gui/src/pages/chat/turns/runAgentConversationTurn.ts index 48647b68..8f79546f 100644 --- a/crates/agent-gui/src/pages/chat/turns/runAgentConversationTurn.ts +++ b/crates/agent-gui/src/pages/chat/turns/runAgentConversationTurn.ts @@ -29,6 +29,7 @@ import { attachToolResultToRound, collapseThinking, type LiveRound, + markToolCallRunningInRound, updateLiveRound, upsertHostedSearchToRound, upsertToolCallToRound, @@ -726,9 +727,10 @@ export async function runAgentConversationTurn(params: RunAgentConversationTurnP (prev) => { let next = prev; for (const { round, toolCall } of deltas) { - next = updateLiveRound(next, round, (target) => - upsertToolCallToRound(collapseThinking(target), toolCall), - ); + next = updateLiveRound(next, round, (target) => { + const withToolCall = upsertToolCallToRound(collapseThinking(target), toolCall); + return markToolCallRunningInRound(withToolCall, toolCall); + }); } return next; }, @@ -901,7 +903,8 @@ export async function runAgentConversationTurn(params: RunAgentConversationTurnP (prev) => updateLiveRound(prev, round, (target) => { const nextTarget = collapseThinking(target); - return upsertToolCallToRound(nextTarget, toolCall); + const withToolCall = upsertToolCallToRound(nextTarget, toolCall); + return markToolCallRunningInRound(withToolCall, toolCall); }), transcriptStore, isConversationVisible(), @@ -930,14 +933,7 @@ export async function runAgentConversationTurn(params: RunAgentConversationTurnP (prev) => updateLiveRound(prev, round, (target) => { const withToolCall = upsertToolCallToRound(collapseThinking(target), toolCall); - const runningIds = withToolCall.runningToolCallIds || []; - const nextRunning = runningIds.includes(toolCall.id) - ? runningIds - : [...runningIds, toolCall.id]; - return { - ...withToolCall, - runningToolCallIds: nextRunning, - }; + return markToolCallRunningInRound(withToolCall, toolCall); }), transcriptStore, isConversationVisible(), @@ -1221,9 +1217,10 @@ export async function runAgentConversationTurn(params: RunAgentConversationTurnP }); batchLiveRoundsUpdate( (prev) => - updateLiveRound(prev, round, (target) => - upsertToolCallToRound(collapseThinking(target), toolCall), - ), + updateLiveRound(prev, round, (target) => { + const withToolCall = upsertToolCallToRound(collapseThinking(target), toolCall); + return markToolCallRunningInRound(withToolCall, toolCall); + }), transcriptStore, isConversationVisible(), ); @@ -1242,13 +1239,7 @@ export async function runAgentConversationTurn(params: RunAgentConversationTurnP (prev) => updateLiveRound(prev, round, (target) => { const withToolCall = upsertToolCallToRound(collapseThinking(target), toolCall); - const runningIds = withToolCall.runningToolCallIds || []; - return { - ...withToolCall, - runningToolCallIds: runningIds.includes(toolCall.id) - ? runningIds - : [...runningIds, toolCall.id], - }; + return markToolCallRunningInRound(withToolCall, toolCall); }), transcriptStore, isConversationVisible(), diff --git a/crates/agent-gui/test/chat/messages.test.mjs b/crates/agent-gui/test/chat/messages.test.mjs index 9d52fe79..b73a1929 100644 --- a/crates/agent-gui/test/chat/messages.test.mjs +++ b/crates/agent-gui/test/chat/messages.test.mjs @@ -1709,6 +1709,79 @@ test("streaming Write and Edit tool previews expose bounded live argument previe assert.equal(editPreview.replaceAll, true); }); +test("visible live tool calls are marked running as soon as their cards appear", () => { + const round = { + round: 1, + blocks: [], + key: "live-tools-running", + runningToolCallIds: [], + thinkingOpen: false, + }; + const toolCalls = [ + { + type: "toolCall", + id: "call-write-live", + name: "Write", + arguments: { path: "report.md", content: "partial content" }, + }, + { + type: "toolCall", + id: "call-edit-live", + name: "Edit", + arguments: { path: "report.md", old_string: "before", new_string: "after" }, + }, + { + type: "toolCall", + id: "call-bash-live", + name: "Bash", + arguments: { command: "pnpm test" }, + }, + { + type: "toolCall", + id: "call-agent:agent:1", + name: "Agent", + arguments: { + delegate_agent_card: true, + parent_tool_call_id: "call-agent", + index: 1, + total: 1, + id: "reviewer", + name: "Reviewer", + prompt: "Review the change.", + }, + }, + ]; + + let running = round; + for (const toolCall of toolCalls) { + running = uiMessages.upsertToolCallToRound(running, toolCall); + running = uiMessages.markToolCallRunningInRound(running, toolCall); + } + const repeated = uiMessages.markToolCallRunningInRound(running, toolCalls[0]); + + assert.deepEqual(running.runningToolCallIds, [ + "call-write-live", + "call-edit-live", + "call-bash-live", + "call-agent:agent:1", + ]); + assert.strictEqual(repeated, running); + + const hiddenParentAgent = { + type: "toolCall", + id: "call-agent", + name: "Agent", + arguments: { + agent_spec: "@agent id=reviewer\nprompt: Review the change.", + }, + }; + const parentOnly = uiMessages.markToolCallRunningInRound( + uiMessages.upsertToolCallToRound(round, hiddenParentAgent), + hiddenParentAgent, + ); + assert.deepEqual(parentOnly.runningToolCallIds, []); +}); + test("seed tool call recovery strips repeated historical tool call text without duplicating native calls", () => { const assistant = { role: "assistant",