Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

139 lines
3.8 KiB
Go

// Copyright 2025 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runtime
import (
"fmt"
"io"
"os"
"time"
)
// CommandStatus describes the lifecycle state of a command.
type CommandStatus struct {
Session string `json:"session"`
Running bool `json:"running"`
ExitCode *int `json:"exit_code,omitempty"`
Error string `json:"error,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
FinishedAt *time.Time `json:"finished_at,omitempty"`
Content string `json:"content,omitempty"`
}
// CommandOutput contains non-streamed stdout/stderr plus status.
type CommandOutput struct {
CommandStatus
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
}
func (c *Controller) commandSnapshot(session string) *commandKernel {
c.mu.RLock()
defer c.mu.RUnlock()
var kernel *commandKernel
if v, ok := c.commandClientMap.Load(session); ok {
kernel, _ = v.(*commandKernel)
}
if kernel == nil {
return nil
}
cp := *kernel
return &cp
}
// GetCommandStatus returns the execution status for a command session.
func (c *Controller) GetCommandStatus(session string) (*CommandStatus, error) {
kernel := c.commandSnapshot(session)
if kernel == nil {
return nil, fmt.Errorf("command not found: %s", session)
}
status := &CommandStatus{
Session: session,
Running: kernel.running,
ExitCode: kernel.exitCode,
Error: kernel.errMsg,
StartedAt: kernel.startedAt,
FinishedAt: kernel.finishedAt,
Content: kernel.content,
}
return status, nil
}
// SeekBackgroundCommandOutput returns accumulated stdout/stderr and status for a session.
func (c *Controller) SeekBackgroundCommandOutput(session string, cursor int64) ([]byte, int64, error) {
kernel := c.commandSnapshot(session)
if kernel == nil {
return nil, -1, fmt.Errorf("command not found: %s", session)
}
if !kernel.isBackground {
return nil, -1, fmt.Errorf("command %s is not running in background", session)
}
file, err := os.Open(kernel.stdoutPath)
if err != nil {
return nil, -1, fmt.Errorf("error open combined output file for command %s: %w", session, err)
}
defer file.Close()
// Seek to the cursor position
_, err = file.Seek(cursor, 0)
if err != nil {
return nil, -1, fmt.Errorf("error seek file: %w", err)
}
// Read all content from cursor to end
data, err := io.ReadAll(file)
if err != nil {
return nil, -1, fmt.Errorf("error read file: %w", err)
}
// Get current file position (end of file)
currentPos, err := file.Seek(0, 1)
if err != nil {
return nil, -1, fmt.Errorf("error get current position: %w", err)
}
return data, currentPos, nil
}
// markCommandFinished updates bookkeeping when a command exits.
func (c *Controller) markCommandFinished(session string, exitCode int, errMsg string) {
now := time.Now()
c.mu.Lock()
defer c.mu.Unlock()
var kernel *commandKernel
if v, ok := c.commandClientMap.Load(session); ok {
kernel, _ = v.(*commandKernel)
}
if kernel == nil {
return
}
kernel.exitCode = &exitCode
kernel.errMsg = errMsg
kernel.running = false
kernel.finishedAt = &now
// Clear the PID so a late or retried Interrupt cannot signal a recycled
// process. Group-wide kill would otherwise amplify the impact of a
// stale-PID hit to every process in the unrelated process group.
kernel.pid = 0
}