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
This commit is contained in:
Max 2026-03-15 13:52:38 +08:00
parent a5c8109db8
commit 89e5840490
7 changed files with 50 additions and 34 deletions

View file

@ -1142,7 +1142,7 @@ jobs:
-v /tmp/kubeconfig-tai-k8s.yml:/etc/tai/kubeconfig.yml:ro \ -v /tmp/kubeconfig-tai-k8s.yml:/etc/tai/kubeconfig.yml:ro \
-e TAI_K8S_UPSTREAM="tcp://${K3D_IP}:6443" \ -e TAI_K8S_UPSTREAM="tcp://${K3D_IP}:6443" \
-e TAI_KUBECONFIG=/etc/tai/kubeconfig.yml \ -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 -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 for i in $(seq 1 30); do

View file

@ -853,7 +853,7 @@ jobs:
-v /tmp/kubeconfig-tai-k8s.yml:/etc/tai/kubeconfig.yml:ro \ -v /tmp/kubeconfig-tai-k8s.yml:/etc/tai/kubeconfig.yml:ro \
-e TAI_K8S_UPSTREAM="tcp://${K3D_IP}:6443" \ -e TAI_K8S_UPSTREAM="tcp://${K3D_IP}:6443" \
-e TAI_KUBECONFIG=/etc/tai/kubeconfig.yml \ -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 -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 for i in $(seq 1 30); do

View file

@ -227,32 +227,30 @@ func (s *streamState) handleToolCall(data []byte) int {
tc := toolCallArray[0] tc := toolCallArray[0]
props = map[string]interface{}{} props = map[string]interface{}{}
hasStaticFields := false hasIdentity := false
if id, ok := tc["id"].(string); ok { if id, ok := tc["id"].(string); ok {
props["id"] = id props["id"] = id
hasStaticFields = true hasIdentity = true
} }
if typ, ok := tc["type"].(string); ok { if typ, ok := tc["type"].(string); ok {
props["type"] = typ props["type"] = typ
hasStaticFields = true hasIdentity = true
} }
if index, ok := tc["index"].(float64); ok { if index, ok := tc["index"].(float64); ok {
props["index"] = int(index) props["index"] = int(index)
hasStaticFields = true
} }
if fn, ok := tc["function"].(map[string]interface{}); ok { if fn, ok := tc["function"].(map[string]interface{}); ok {
if name, ok := fn["name"].(string); ok { if name, ok := fn["name"].(string); ok {
props["name"] = name props["name"] = name
hasStaticFields = true hasIdentity = true
} }
if args, ok := fn["arguments"].(string); ok { if args, ok := fn["arguments"].(string); ok {
props["arguments"] = args props["arguments"] = args
} }
} }
if hasStaticFields { if hasIdentity {
// First chunk with id/name/type: merge so all fields are applied. // 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" deltaAction = "merge"
} else if _, ok := props["arguments"]; ok { } else if _, ok := props["arguments"]; ok {
// Subsequent chunk with only arguments fragment: append to arguments. // Subsequent chunk with only arguments fragment: append to arguments.

View file

@ -116,7 +116,6 @@ func (ast *Assistant) initSandboxV2(ctx *context.Context, opts *context.Options)
} }
// 7. Runner.Prepare (standard context). // 7. Runner.Prepare (standard context).
updateLoadingV2(ctx, loadingMsgID, "sandbox.configuring")
err = runner.Prepare(stdCtx, &sandboxTypes.PrepareRequest{ err = runner.Prepare(stdCtx, &sandboxTypes.PrepareRequest{
Computer: computer, Computer: computer,
Config: cfg, Config: cfg,

View file

@ -100,14 +100,14 @@ func init() {
"kb.chat.description": "Auto-created knowledge base collection for chat sessions", "kb.chat.description": "Auto-created knowledge base collection for chat sessions",
// Sandbox: assistant/sandbox.go - Sandbox status messages // Sandbox: assistant/sandbox.go - Sandbox status messages
"sandbox.preparing": "Preparing sandbox environment", "sandbox.preparing": "Getting things ready...",
"sandbox.ready": "Sandbox ready", "sandbox.ready": "Sandbox ready",
"sandbox.working": "Working on your request", "sandbox.working": "Working on your request",
"sandbox.completed": "Completed", "sandbox.completed": "Completed",
"sandbox.failed": "Execution failed", "sandbox.failed": "Execution failed",
"sandbox.starting": "Starting sandbox environment", "sandbox.starting": "Setting up workspace...",
"sandbox.configuring": "Configuring runtime environment", "sandbox.pulling_image": "Preparing environment (first time may take a moment)",
"sandbox.pulling_image": "Pulling container image", "sandbox.waiting_response": "Waiting for AI response...",
// Sandbox: claude/executor.go - Tool execution messages // Sandbox: claude/executor.go - Tool execution messages
"sandbox.tool.read": "Reading file", "sandbox.tool.read": "Reading file",
@ -227,14 +227,14 @@ func init() {
"kb.chat.description": "自动为聊天会话创建的知识库集合", "kb.chat.description": "自动为聊天会话创建的知识库集合",
// Sandbox: assistant/sandbox.go - Sandbox status messages // Sandbox: assistant/sandbox.go - Sandbox status messages
"sandbox.preparing": "正在准备沙箱环境", "sandbox.preparing": "正在准备...",
"sandbox.ready": "沙箱环境就绪", "sandbox.ready": "就绪",
"sandbox.working": "正在处理您的请求", "sandbox.working": "正在处理您的请求",
"sandbox.completed": "处理完成", "sandbox.completed": "处理完成",
"sandbox.failed": "执行失败", "sandbox.failed": "执行失败",
"sandbox.starting": "正在启动沙箱环境", "sandbox.starting": "正在启动工作区...",
"sandbox.configuring": "正在配置运行环境", "sandbox.pulling_image": "正在准备运行环境(首次可能需要一点时间)",
"sandbox.pulling_image": "正在拉取容器镜像", "sandbox.waiting_response": "等待 AI 响应...",
// Sandbox: claude/executor.go - Tool execution messages // Sandbox: claude/executor.go - Tool execution messages
"sandbox.tool.read": "正在读取文件", "sandbox.tool.read": "正在读取文件",
@ -382,14 +382,14 @@ func init() {
"kb.chat.description": "自动为聊天会话创建的知识库集合", "kb.chat.description": "自动为聊天会话创建的知识库集合",
// Sandbox: assistant/sandbox.go - Sandbox status messages // Sandbox: assistant/sandbox.go - Sandbox status messages
"sandbox.preparing": "正在准备沙箱环境", "sandbox.preparing": "正在准备...",
"sandbox.ready": "沙箱环境就绪", "sandbox.ready": "就绪",
"sandbox.working": "正在处理您的请求", "sandbox.working": "正在处理您的请求",
"sandbox.completed": "处理完成", "sandbox.completed": "处理完成",
"sandbox.failed": "执行失败", "sandbox.failed": "执行失败",
"sandbox.starting": "正在启动沙箱环境", "sandbox.starting": "正在启动工作区...",
"sandbox.configuring": "正在配置运行环境", "sandbox.pulling_image": "正在准备运行环境(首次可能需要一点时间)",
"sandbox.pulling_image": "正在拉取容器镜像", "sandbox.waiting_response": "等待 AI 响应...",
// Sandbox: claude/executor.go - Tool execution messages // Sandbox: claude/executor.go - Tool execution messages
"sandbox.tool.read": "正在读取文件", "sandbox.tool.read": "正在读取文件",

View file

@ -78,6 +78,10 @@ func parseStreamJSON(_ context.Context, stdout io.ReadCloser, handler message.St
toolIndex++ toolIndex++
if handler != nil { if handler != nil {
if messageStarted {
handler(message.ChunkMessageEnd, nil)
messageStarted = false
}
if !toolBlockActive { if !toolBlockActive {
startData := message.EventMessageStartData{ startData := message.EventMessageStartData{
MessageID: fmt.Sprintf("sandbox-tool-%d", time.Now().UnixNano()), 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 { if toolBlockActive {
handler(message.ChunkMessageEnd, nil) handler(message.ChunkMessageEnd, nil)
toolBlockActive = false toolBlockActive = false
messageStarted = false
} }
if !messageStarted { if !messageStarted {
startData := message.EventMessageStartData{ startData := message.EventMessageStartData{

View file

@ -8,6 +8,7 @@ import (
"time" "time"
agentContext "github.com/yaoapp/yao/agent/context" 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/output/message"
"github.com/yaoapp/yao/agent/sandbox/v2/types" "github.com/yaoapp/yao/agent/sandbox/v2/types"
infra "github.com/yaoapp/yao/sandbox/v2" 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 var textContent []byte
loadingClosed := false loadingClosed := false
wrappedHandler := func(chunkType message.StreamChunkType, data []byte) int { wrappedHandler := func(chunkType message.StreamChunkType, data []byte) int {