Add guest sessions and agent VM defaults
Add daemon-backed workspace and guest-session primitives so host orchestrators can prepare /root/repo, launch long-lived guest commands, and attach to pipe-mode sessions over the local stdio mux bridge. Persist richer session metadata and launch diagnostics, preflight guest cwd/command requirements, make pipe-mode attach rehydratable from guest state after daemon restart, and allow submodules when workspace prepare runs in full_copy mode. At the same time, stop vm run from auto-attaching opencode, make it print next-step commands instead, and make glibc guest images more agent-ready by installing node, opencode, claude, and pi while syncing opencode/claude/pi auth files into work disks on VM start. Validation: - GOCACHE=/tmp/banger-gocache go test ./... - make build - banger vm workspace prepare --help - banger vm session --help - banger vm session start --help - banger vm session attach --help
This commit is contained in:
parent
497e6dca3d
commit
37c4c091ec
18 changed files with 3212 additions and 405 deletions
76
internal/sessionstream/sessionstream.go
Normal file
76
internal/sessionstream/sessionstream.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package sessionstream
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
ChannelStdin byte = 0x01
|
||||
ChannelStdout byte = 0x02
|
||||
ChannelStderr byte = 0x03
|
||||
ChannelControl byte = 0x04
|
||||
FormatV1 = "stdio_mux_v1"
|
||||
)
|
||||
|
||||
type ControlMessage struct {
|
||||
Type string `json:"type"`
|
||||
ExitCode *int `json:"exit_code,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func WriteFrame(w io.Writer, channel byte, payload []byte) error {
|
||||
var header [5]byte
|
||||
header[0] = channel
|
||||
binary.BigEndian.PutUint32(header[1:], uint32(len(payload)))
|
||||
if _, err := w.Write(header[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := w.Write(payload)
|
||||
return err
|
||||
}
|
||||
|
||||
func ReadFrame(r io.Reader) (byte, []byte, error) {
|
||||
var header [5]byte
|
||||
if _, err := io.ReadFull(r, header[:]); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
length := binary.BigEndian.Uint32(header[1:])
|
||||
payload := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, payload); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return header[0], payload, nil
|
||||
}
|
||||
|
||||
func WriteControl(w io.Writer, message ControlMessage) error {
|
||||
payload, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteFrame(w, ChannelControl, payload)
|
||||
}
|
||||
|
||||
func ReadControl(payload []byte) (ControlMessage, error) {
|
||||
var message ControlMessage
|
||||
if err := json.Unmarshal(payload, &message); err != nil {
|
||||
return ControlMessage{}, err
|
||||
}
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func ReadNextControl(r io.Reader) (ControlMessage, error) {
|
||||
channel, payload, err := ReadFrame(r)
|
||||
if err != nil {
|
||||
return ControlMessage{}, err
|
||||
}
|
||||
if channel != ChannelControl {
|
||||
return ControlMessage{}, fmt.Errorf("unexpected channel %d", channel)
|
||||
}
|
||||
return ReadControl(payload)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue