From 89e5840490c10e1d4179849f35657f76f0eae9c6 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 15 Mar 2026 13:52:38 +0800 Subject: [PATCH] feat(sandbox): update sandbox status messages and improve loading feedback - Modified Docker run commands in CI workflows to include the `-direct` flag for enhanced server operation. - Removed outdated loading status message during sandbox preparation and added new messages for improved user feedback. - Introduced a loading message for waiting on AI responses, enhancing the user experience during sandbox execution. - Refactored identity handling in stream processing to streamline message management. Made-with: Cursor --- .github/workflows/pr-test.yml | 2 +- .github/workflows/unit-test.yml | 2 +- agent/assistant/handlers/stream.go | 12 ++++---- agent/assistant/sandbox_v2.go | 1 - agent/i18n/builtin.go | 48 +++++++++++++++--------------- agent/sandbox/v2/claude/parse.go | 5 ++++ agent/sandbox/v2/stream.go | 14 +++++++++ 7 files changed, 50 insertions(+), 34 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 01e3f935..92f975da 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -1142,7 +1142,7 @@ jobs: -v /tmp/kubeconfig-tai-k8s.yml:/etc/tai/kubeconfig.yml:ro \ -e TAI_K8S_UPSTREAM="tcp://${K3D_IP}:6443" \ -e TAI_KUBECONFIG=/etc/tai/kubeconfig.yml \ - yaoapp/tai:latest server \ + yaoapp/tai:latest server -direct \ -grpc 0.0.0.0:19100 -http 0.0.0.0:8099 -vnc 0.0.0.0:16080 -k8s 0.0.0.0:16443 for i in $(seq 1 30); do diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index fa4a8061..2de1fb81 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -853,7 +853,7 @@ jobs: -v /tmp/kubeconfig-tai-k8s.yml:/etc/tai/kubeconfig.yml:ro \ -e TAI_K8S_UPSTREAM="tcp://${K3D_IP}:6443" \ -e TAI_KUBECONFIG=/etc/tai/kubeconfig.yml \ - yaoapp/tai:latest server \ + yaoapp/tai:latest server -direct \ -grpc 0.0.0.0:19100 -http 0.0.0.0:8099 -vnc 0.0.0.0:16080 -k8s 0.0.0.0:16443 for i in $(seq 1 30); do diff --git a/agent/assistant/handlers/stream.go b/agent/assistant/handlers/stream.go index 924c8125..1d0d1b6b 100644 --- a/agent/assistant/handlers/stream.go +++ b/agent/assistant/handlers/stream.go @@ -227,32 +227,30 @@ func (s *streamState) handleToolCall(data []byte) int { tc := toolCallArray[0] props = map[string]interface{}{} - hasStaticFields := false + hasIdentity := false if id, ok := tc["id"].(string); ok { props["id"] = id - hasStaticFields = true + hasIdentity = true } if typ, ok := tc["type"].(string); ok { props["type"] = typ - hasStaticFields = true + hasIdentity = true } if index, ok := tc["index"].(float64); ok { props["index"] = int(index) - hasStaticFields = true } if fn, ok := tc["function"].(map[string]interface{}); ok { if name, ok := fn["name"].(string); ok { props["name"] = name - hasStaticFields = true + hasIdentity = true } if args, ok := fn["arguments"].(string); ok { props["arguments"] = args } } - if hasStaticFields { + if hasIdentity { // First chunk with id/name/type: merge so all fields are applied. - // arguments="" is included but that's fine — subsequent appends build on it. deltaAction = "merge" } else if _, ok := props["arguments"]; ok { // Subsequent chunk with only arguments fragment: append to arguments. diff --git a/agent/assistant/sandbox_v2.go b/agent/assistant/sandbox_v2.go index 772066d6..027e07b4 100644 --- a/agent/assistant/sandbox_v2.go +++ b/agent/assistant/sandbox_v2.go @@ -116,7 +116,6 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options) } // 7. Runner.Prepare (standard context). - updateLoadingV2(ctx, loadingMsgID, "sandbox.configuring") err = runner.Prepare(stdCtx, &sandboxTypes.PrepareRequest{ Computer: computer, Config: cfg, diff --git a/agent/i18n/builtin.go b/agent/i18n/builtin.go index ad18ea1b..c6bdb1e7 100644 --- a/agent/i18n/builtin.go +++ b/agent/i18n/builtin.go @@ -100,14 +100,14 @@ func init() { "kb.chat.description": "Auto-created knowledge base collection for chat sessions", // Sandbox: assistant/sandbox.go - Sandbox status messages - "sandbox.preparing": "Preparing sandbox environment", - "sandbox.ready": "Sandbox ready", - "sandbox.working": "Working on your request", - "sandbox.completed": "Completed", - "sandbox.failed": "Execution failed", - "sandbox.starting": "Starting sandbox environment", - "sandbox.configuring": "Configuring runtime environment", - "sandbox.pulling_image": "Pulling container image", + "sandbox.preparing": "Getting things ready...", + "sandbox.ready": "Sandbox ready", + "sandbox.working": "Working on your request", + "sandbox.completed": "Completed", + "sandbox.failed": "Execution failed", + "sandbox.starting": "Setting up workspace...", + "sandbox.pulling_image": "Preparing environment (first time may take a moment)", + "sandbox.waiting_response": "Waiting for AI response...", // Sandbox: claude/executor.go - Tool execution messages "sandbox.tool.read": "Reading file", @@ -227,14 +227,14 @@ func init() { "kb.chat.description": "自动为聊天会话创建的知识库集合", // Sandbox: assistant/sandbox.go - Sandbox status messages - "sandbox.preparing": "正在准备沙箱环境", - "sandbox.ready": "沙箱环境就绪", - "sandbox.working": "正在处理您的请求", - "sandbox.completed": "处理完成", - "sandbox.failed": "执行失败", - "sandbox.starting": "正在启动沙箱环境", - "sandbox.configuring": "正在配置运行环境", - "sandbox.pulling_image": "正在拉取容器镜像", + "sandbox.preparing": "正在准备...", + "sandbox.ready": "就绪", + "sandbox.working": "正在处理您的请求", + "sandbox.completed": "处理完成", + "sandbox.failed": "执行失败", + "sandbox.starting": "正在启动工作区...", + "sandbox.pulling_image": "正在准备运行环境(首次可能需要一点时间)", + "sandbox.waiting_response": "等待 AI 响应...", // Sandbox: claude/executor.go - Tool execution messages "sandbox.tool.read": "正在读取文件", @@ -382,14 +382,14 @@ func init() { "kb.chat.description": "自动为聊天会话创建的知识库集合", // Sandbox: assistant/sandbox.go - Sandbox status messages - "sandbox.preparing": "正在准备沙箱环境", - "sandbox.ready": "沙箱环境就绪", - "sandbox.working": "正在处理您的请求", - "sandbox.completed": "处理完成", - "sandbox.failed": "执行失败", - "sandbox.starting": "正在启动沙箱环境", - "sandbox.configuring": "正在配置运行环境", - "sandbox.pulling_image": "正在拉取容器镜像", + "sandbox.preparing": "正在准备...", + "sandbox.ready": "就绪", + "sandbox.working": "正在处理您的请求", + "sandbox.completed": "处理完成", + "sandbox.failed": "执行失败", + "sandbox.starting": "正在启动工作区...", + "sandbox.pulling_image": "正在准备运行环境(首次可能需要一点时间)", + "sandbox.waiting_response": "等待 AI 响应...", // Sandbox: claude/executor.go - Tool execution messages "sandbox.tool.read": "正在读取文件", diff --git a/agent/sandbox/v2/claude/parse.go b/agent/sandbox/v2/claude/parse.go index 49f8e5c6..af8c2a6c 100644 --- a/agent/sandbox/v2/claude/parse.go +++ b/agent/sandbox/v2/claude/parse.go @@ -78,6 +78,10 @@ func parseStreamJSON(_ context.Context, stdout io.ReadCloser, handler message.St toolIndex++ if handler != nil { + if messageStarted { + handler(message.ChunkMessageEnd, nil) + messageStarted = false + } if !toolBlockActive { startData := message.EventMessageStartData{ MessageID: fmt.Sprintf("sandbox-tool-%d", time.Now().UnixNano()), @@ -119,6 +123,7 @@ func parseStreamJSON(_ context.Context, stdout io.ReadCloser, handler message.St if toolBlockActive { handler(message.ChunkMessageEnd, nil) toolBlockActive = false + messageStarted = false } if !messageStarted { startData := message.EventMessageStartData{ diff --git a/agent/sandbox/v2/stream.go b/agent/sandbox/v2/stream.go index 50a5d94e..4816af04 100644 --- a/agent/sandbox/v2/stream.go +++ b/agent/sandbox/v2/stream.go @@ -8,6 +8,7 @@ import ( "time" agentContext "github.com/yaoapp/yao/agent/context" + "github.com/yaoapp/yao/agent/i18n" "github.com/yaoapp/yao/agent/output/message" "github.com/yaoapp/yao/agent/sandbox/v2/types" infra "github.com/yaoapp/yao/sandbox/v2" @@ -106,6 +107,19 @@ func ExecuteSandboxStream( } }() + if req.LoadingMsgID != "" { + waitMsg := &message.Message{ + MessageID: req.LoadingMsgID, + Delta: true, + DeltaAction: message.DeltaReplace, + Type: message.TypeLoading, + Props: map[string]any{ + "message": i18n.T(ctx.Locale, "sandbox.waiting_response"), + }, + } + ctx.Send(waitMsg) + } + var textContent []byte loadingClosed := false wrappedHandler := func(chunkType message.StreamChunkType, data []byte) int {