feat: add shell interface, registry, and bash shell emulator
Implement Phase 1.4: replaces the hardcoded banner/timeout stub with a proper shell system. Adds a Shell interface with weighted registry for shell selection, a RecordingChannel wrapper (pass-through for now, prep for Phase 2.3 replay), and a bash-like shell with fake filesystem, terminal line reader, and command handling (pwd, ls, cd, cat, whoami, hostname, id, uname, exit). Sessions now log command/output pairs to the store and record the shell name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
43
internal/shell/recorder_test.go
Normal file
43
internal/shell/recorder_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// nopCloser wraps a ReadWriter with a no-op Close.
|
||||
type nopCloser struct {
|
||||
io.ReadWriter
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
func TestRecordingChannelPassthrough(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
rc := NewRecordingChannel(nopCloser{&buf})
|
||||
|
||||
// Write through the recorder.
|
||||
msg := []byte("hello")
|
||||
n, err := rc.Write(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
if n != len(msg) {
|
||||
t.Errorf("Write n = %d, want %d", n, len(msg))
|
||||
}
|
||||
|
||||
// Read through the recorder.
|
||||
out := make([]byte, 16)
|
||||
n, err = rc.Read(out)
|
||||
if err != nil {
|
||||
t.Fatalf("Read: %v", err)
|
||||
}
|
||||
if string(out[:n]) != "hello" {
|
||||
t.Errorf("Read = %q, want %q", out[:n], "hello")
|
||||
}
|
||||
|
||||
if err := rc.Close(); err != nil {
|
||||
t.Fatalf("Close: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user