diff --git a/cmd/olliesrv/bypass_notify.go b/cmd/olliesrv/bypass_notify.go index 1fded7a..406975f 100644 --- a/cmd/olliesrv/bypass_notify.go +++ b/cmd/olliesrv/bypass_notify.go @@ -4,7 +4,7 @@ import ( "fmt" "sync" - client "ollie/client" + toolclient "ollie/cmd/olliesrv/internal/toolclient" "ollie/toolsrv/protocol" "github.com/godbus/dbus/v5" @@ -25,7 +25,7 @@ type pendingRequest struct { } type sessionRef struct { - dialFn func() *client.ToolsrvConn + dialFn func() *toolclient.ToolsrvConn } var notifier *bypassNotifier @@ -53,14 +53,14 @@ func initBypassNotifier(conn *dbus.Conn) { // notifyBypass sends a desktop notification for a bypass request. // When the user responds, it writes the resolution directly to toolsrvclient. -func notifyBypass(req *protocol.BypassRequest, sessionID string, dialFn func() *client.ToolsrvConn) { +func notifyBypass(req *protocol.BypassRequest, sessionID string, dialFn func() *toolclient.ToolsrvConn) { if notifier == nil { return } notifier.sendNotification(req, sessionID, dialFn) } -func (n *bypassNotifier) sendNotification(req *protocol.BypassRequest, sessionID string, dialFn func() *client.ToolsrvConn) { +func (n *bypassNotifier) sendNotification(req *protocol.BypassRequest, sessionID string, dialFn func() *toolclient.ToolsrvConn) { obj := n.conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") summary := "Elevation Request" diff --git a/cmd/olliesrv/internal/agent/agent.go b/cmd/olliesrv/internal/agent/agent.go index db6d942..dbc89fe 100644 --- a/cmd/olliesrv/internal/agent/agent.go +++ b/cmd/olliesrv/internal/agent/agent.go @@ -12,8 +12,8 @@ import ( "9fans.net/go/plan9" p9client "9fans.net/go/plan9/client" - client "ollie/client" "ollie/cmd/olliesrv/internal/backend" + toolclient "ollie/cmd/olliesrv/internal/toolclient" olog "ollie/log" "ollie/util" ) @@ -30,7 +30,7 @@ type Agent struct { agentsDir string systemPrompt string // system prompt for /agent reloads envBlock string // environment block for /agent reloads - newToolServer func() *client.ToolsrvConn + newToolServer func() *toolclient.ToolsrvConn newBackend func(string) (backend.Backend, error) currentAction atomic.Pointer[actionHandle] warnedContext bool @@ -333,7 +333,7 @@ func (ag *Agent) emit(ev Event) { // SetToolServer updates the tool server connection and factory. // Used when resuming a paused session that was restored without infra. -func (ag *Agent) SetToolServer(newToolServer func() *client.ToolsrvConn, conn *client.ToolsrvConn) { +func (ag *Agent) SetToolServer(newToolServer func() *toolclient.ToolsrvConn, conn *toolclient.ToolsrvConn) { ag.newToolServer = newToolServer if ag.runtime != nil { ag.runtime.ToolServer = conn @@ -625,7 +625,7 @@ func (ag *Agent) ListModels() []string { // ToolServer returns the tool execution server, or nil if unavailable. // Exported for use by the 9P filesystem layer to sync tool registries. -func (ag *Agent) ToolServer() *client.ToolsrvConn { +func (ag *Agent) ToolServer() *toolclient.ToolsrvConn { return ag.runtime.ToolServer } @@ -713,7 +713,7 @@ type AgentParams struct { Cwd string // working directory for tool execution SystemPrompt string EnvBlock string - NewToolServer func() *client.ToolsrvConn + NewToolServer func() *toolclient.ToolsrvConn NewBackend func(string) (backend.Backend, error) Log *olog.Logger StartupMsgs []string diff --git a/cmd/olliesrv/internal/agent/loop.go b/cmd/olliesrv/internal/agent/loop.go index 5c250c9..5739359 100644 --- a/cmd/olliesrv/internal/agent/loop.go +++ b/cmd/olliesrv/internal/agent/loop.go @@ -10,8 +10,8 @@ import ( "sync" "time" - client "ollie/client" "ollie/cmd/olliesrv/internal/backend" + toolclient "ollie/cmd/olliesrv/internal/toolclient" "ollie/util" ) @@ -599,7 +599,7 @@ func (ag *Agent) execOne(ctx context.Context, call backend.ToolCall) (toolResult ag.emit(Event{Role: "tool", Name: call.Name, Content: result, OutputFormat: toolOutputFormat(rt, call.Name)}) return toolResult{ToolCallID: call.ID, Name: call.Name, Content: result, IsError: true}, true } - var rlErr *client.RateLimitedError + var rlErr *toolclient.RateLimitedError if errors.As(err, &rlErr) { result = fmt.Sprintf("error: shell is rate-limited — blocked for %v. Do not call shell() again until the block expires. Use other tools or wait.", rlErr.Remaining) } else { diff --git a/cmd/olliesrv/internal/agent/runtime.go b/cmd/olliesrv/internal/agent/runtime.go index a7fff3f..31408ba 100644 --- a/cmd/olliesrv/internal/agent/runtime.go +++ b/cmd/olliesrv/internal/agent/runtime.go @@ -8,9 +8,9 @@ import ( "sort" "strings" - client "ollie/client" "ollie/cmd/olliesrv/internal/backend" "ollie/cmd/olliesrv/internal/prompts" + toolclient "ollie/cmd/olliesrv/internal/toolclient" "ollie/toolsrv/protocol" ) @@ -139,7 +139,7 @@ func RenderTools(infos []protocol.ToolInfo) string { // agents replaces it atomically. type Runtime struct { Backend backend.Backend - ToolServer *client.ToolsrvConn + ToolServer *toolclient.ToolsrvConn Preamble *Preamble Tools []backend.Tool ToolMeta map[string]protocol.ToolInfo @@ -161,7 +161,7 @@ func (rt *Runtime) PreambleString() string { // BuildRuntime constructs a Runtime from a pre-configured tool server and // optional agent config. -func BuildRuntime(cfg *AgentConfig, srv *client.ToolsrvConn, cwd string, env []string, systemPrompt, envBlock string) *Runtime { +func BuildRuntime(cfg *AgentConfig, srv *toolclient.ToolsrvConn, cwd string, env []string, systemPrompt, envBlock string) *Runtime { var messages []string var allToolInfos []protocol.ToolInfo diff --git a/cmd/olliesrv/internal/session/session.go b/cmd/olliesrv/internal/session/session.go index d457244..001ef1c 100644 --- a/cmd/olliesrv/internal/session/session.go +++ b/cmd/olliesrv/internal/session/session.go @@ -9,7 +9,6 @@ import ( "sync" "time" - client "ollie/client" "ollie/cmd/olliesrv/internal/agent" "ollie/cmd/olliesrv/internal/backend" "ollie/cmd/olliesrv/internal/toolclient" @@ -39,8 +38,8 @@ type Session struct { // Mutable state protected by mu mu sync.RWMutex - name string // friendly name - toolsConn *client.ToolsrvConn // multiplexed RPC connection + name string // friendly name + toolsConn *toolclient.ToolsrvConn // multiplexed RPC connection paused bool // Autosave @@ -85,7 +84,7 @@ func (s *Session) SetName(name string) { } // ToolsConn returns the tool server connection. -func (s *Session) ToolsConn() *client.ToolsrvConn { +func (s *Session) ToolsConn() *toolclient.ToolsrvConn { s.mu.RLock() defer s.mu.RUnlock() return s.toolsConn @@ -93,7 +92,7 @@ func (s *Session) ToolsConn() *client.ToolsrvConn { // DialToolServer dials a fresh toolsrv connection via the keeper. // Caller is responsible for closing the returned conn. -func (s *Session) DialToolServer() *client.ToolsrvConn { +func (s *Session) DialToolServer() *toolclient.ToolsrvConn { s.mu.RLock() k := s.Keeper s.mu.RUnlock() @@ -108,7 +107,7 @@ func (s *Session) DialToolServer() *client.ToolsrvConn { } // SetToolsConn sets the tool server connection. -func (s *Session) SetToolsConn(conn *client.ToolsrvConn) { +func (s *Session) SetToolsConn(conn *toolclient.ToolsrvConn) { s.mu.Lock() defer s.mu.Unlock() s.toolsConn = conn @@ -116,7 +115,7 @@ func (s *Session) SetToolsConn(conn *client.ToolsrvConn) { // BypassNotifyFunc is called when a bypass request arrives. // It receives the request, session ID, and a dial function to get a connection for resolution. -type BypassNotifyFunc func(req *protocol.BypassRequest, sessionID string, dialFn func() *client.ToolsrvConn) +type BypassNotifyFunc func(req *protocol.BypassRequest, sessionID string, dialFn func() *toolclient.ToolsrvConn) // StartBypassLoop starts a goroutine that reads bypass requests from toolsrv // and calls notifyFn for each one. This should be called after SetToolsConn. @@ -327,7 +326,7 @@ func (s *Session) Resume() error { if err != nil { return fmt.Errorf("resume agent dial: %w", err) } - ag.SetToolServer(func() *client.ToolsrvConn { + ag.SetToolServer(func() *toolclient.ToolsrvConn { c, _ := s.Keeper.Dial() return c }, agConn) @@ -393,7 +392,7 @@ func (s *Session) LoadTool(name string, ag *agent.Agent) error { } // LoadToolOnConn loads a tool on the given connection. -func LoadToolOnConn(conn *client.ToolsrvConn, name string) error { +func LoadToolOnConn(conn *toolclient.ToolsrvConn, name string) error { if conn == nil { return nil } diff --git a/cmd/olliesrv/internal/session/setup.go b/cmd/olliesrv/internal/session/setup.go index 6317fa5..611de3b 100644 --- a/cmd/olliesrv/internal/session/setup.go +++ b/cmd/olliesrv/internal/session/setup.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - client "ollie/client" "ollie/cmd/olliesrv/internal/agent" "ollie/cmd/olliesrv/internal/prompts" "ollie/cmd/olliesrv/internal/toolclient" @@ -16,8 +15,8 @@ import ( type SessionInfra struct { Proc *toolclient.Process Keeper *toolclient.ProcessKeeper - ToolsConn *client.ToolsrvConn - NewToolServer func() *client.ToolsrvConn + ToolsConn *toolclient.ToolsrvConn + NewToolServer func() *toolclient.ToolsrvConn Platform string IsGitRepo bool } @@ -39,7 +38,7 @@ type ToolServerConfig struct { type InfraConfig struct { Proc *toolclient.Process Keeper *toolclient.ProcessKeeper - ToolsConn *client.ToolsrvConn + ToolsConn *toolclient.ToolsrvConn } // SetupToolServer spawns the tool server process and creates the infrastructure. @@ -50,9 +49,9 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) { // Reuse existing infrastructure if provided. if cfg.ReuseFrom != nil { r := cfg.ReuseFrom - var newToolServer func() *client.ToolsrvConn + var newToolServer func() *toolclient.ToolsrvConn if r.Keeper != nil { - newToolServer = func() *client.ToolsrvConn { + newToolServer = func() *toolclient.ToolsrvConn { conn, err := r.Keeper.Dial() if err != nil { return nil @@ -60,7 +59,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) { return conn } } else { - newToolServer = func() *client.ToolsrvConn { + newToolServer = func() *toolclient.ToolsrvConn { return r.ToolsConn } } @@ -79,7 +78,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) { } var proc *toolclient.Process var keeper *toolclient.ProcessKeeper - var newToolServer func() *client.ToolsrvConn + var newToolServer func() *toolclient.ToolsrvConn var err error var platform string var isGitRepo bool @@ -102,7 +101,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) { Yolo: cfg.Yolo, }) }) - newToolServer = func() *client.ToolsrvConn { + newToolServer = func() *toolclient.ToolsrvConn { conn, err := keeper.Dial() if err != nil { return nil @@ -126,7 +125,7 @@ func SetupToolServer(cfg ToolServerConfig) (*SessionInfra, error) { keeper = toolclient.NewProcessKeeper(cfg.Ctx, proc, func(ctx context.Context) (*toolclient.Process, error) { return toolclient.Spawn(ctx, cfg.CWD, dialOpts...) }) - newToolServer = func() *client.ToolsrvConn { + newToolServer = func() *toolclient.ToolsrvConn { conn, err := keeper.Dial() if err != nil { return nil @@ -173,7 +172,7 @@ func BuildPromptLayers(cfg *agent.AgentConfig, cwd, sessID, uname, platform stri // LoadAutoLoadTools loads all tools from the agent config's autoLoad list. // It first sends the session ID and uname to the tool server so that // ollie-remote can attach its tool registry before processing tool_load RPCs. -func LoadAutoLoadTools(cfg *agent.AgentConfig, conn *client.ToolsrvConn, sessID, uname string, logError func(string, ...any)) { +func LoadAutoLoadTools(cfg *agent.AgentConfig, conn *toolclient.ToolsrvConn, sessID, uname string, logError func(string, ...any)) { if cfg == nil || conn == nil { return } diff --git a/cmd/olliesrv/internal/toolclient/spawn.go b/cmd/olliesrv/internal/toolclient/spawn.go index 548bba2..5651137 100644 --- a/cmd/olliesrv/internal/toolclient/spawn.go +++ b/cmd/olliesrv/internal/toolclient/spawn.go @@ -17,7 +17,6 @@ import ( "syscall" "time" - client "ollie/client" "ollie/util" ) @@ -316,13 +315,13 @@ func (pk *ProcessKeeper) SetContext(ctx context.Context) { } // Dial connects to the managed process, respawning if necessary. -func (pk *ProcessKeeper) Dial() (*client.ToolsrvConn, error) { +func (pk *ProcessKeeper) Dial() (*ToolsrvConn, error) { pk.mu.Lock() defer pk.mu.Unlock() // Try to connect to existing process if pk.proc != nil { - conn, err := client.DialToolsrv(pk.proc.SocketPath, pk.proc.Secret) + conn, err := DialToolsrv(pk.proc.SocketPath, pk.proc.Secret) if err == nil { // Save the secret if this was first connection if pk.proc.Secret == "" { @@ -343,7 +342,7 @@ func (pk *ProcessKeeper) Dial() (*client.ToolsrvConn, error) { return nil, fmt.Errorf("respawn: %w", err) } pk.proc = proc - conn, err := client.DialToolsrv(proc.SocketPath, proc.Secret) + conn, err := DialToolsrv(proc.SocketPath, proc.Secret) if err != nil { return nil, err } diff --git a/client/toolsrv.go b/cmd/olliesrv/internal/toolclient/toolsrv.go similarity index 99% rename from client/toolsrv.go rename to cmd/olliesrv/internal/toolclient/toolsrv.go index c3dae18..5a3d0b4 100644 --- a/client/toolsrv.go +++ b/cmd/olliesrv/internal/toolclient/toolsrv.go @@ -1,5 +1,5 @@ // client9p.go - 9P client for toolsrvclient. -package client +package toolclient import ( "context" diff --git a/cmd/toolsrv/integration_test.go b/cmd/olliesrv/internal/toolclient/toolsrv_integration_test.go similarity index 93% rename from cmd/toolsrv/integration_test.go rename to cmd/olliesrv/internal/toolclient/toolsrv_integration_test.go index 9fc2ba0..b6769ac 100644 --- a/cmd/toolsrv/integration_test.go +++ b/cmd/olliesrv/internal/toolclient/toolsrv_integration_test.go @@ -2,7 +2,7 @@ // // These tests build and spawn a real toolsrv process, then connect via 9P // to verify the authentication flow works correctly. -package main +package toolclient import ( "context" @@ -15,7 +15,6 @@ import ( "testing" "time" - client "ollie/client" "ollie/toolsrv/metadata" "ollie/toolsrv/protocol" ) @@ -142,7 +141,7 @@ func TestIntegration_FirstConnectionSetsSecret(t *testing.T) { // First connection with a secret should succeed secret := "test-secret-first-connection" - conn, err := client.DialToolsrv(socketPath, secret) + conn, err := DialToolsrv(socketPath, secret) if err != nil { t.Fatalf("first Dial failed: %v", err) } @@ -166,7 +165,7 @@ func TestIntegration_SameSecretReconnects(t *testing.T) { secret := "test-secret-reconnect" // First connection establishes the secret - conn1, err := client.DialToolsrv(socketPath, secret) + conn1, err := DialToolsrv(socketPath, secret) if err != nil { t.Fatalf("first Dial failed: %v", err) } @@ -174,7 +173,7 @@ func TestIntegration_SameSecretReconnects(t *testing.T) { conn1.Close() // Second connection with same secret should succeed - conn2, err := client.DialToolsrv(socketPath, secret) + conn2, err := DialToolsrv(socketPath, secret) if err != nil { t.Fatalf("second Dial failed: %v", err) } @@ -191,14 +190,14 @@ func TestIntegration_WrongSecretFails(t *testing.T) { defer cleanup() // First connection establishes the secret - conn1, err := client.DialToolsrv(socketPath, "correct-secret") + conn1, err := DialToolsrv(socketPath, "correct-secret") if err != nil { t.Fatalf("first Dial failed: %v", err) } conn1.Close() // Second connection with wrong secret should fail - conn2, err := client.DialToolsrv(socketPath, "wrong-secret") + conn2, err := DialToolsrv(socketPath, "wrong-secret") if err == nil { conn2.Close() t.Fatal("Dial with wrong secret should fail") @@ -210,7 +209,7 @@ func TestIntegration_EmptySecretGeneratesOne(t *testing.T) { defer cleanup() // Empty secret should generate a random one - conn, err := client.DialToolsrv(socketPath, "") + conn, err := DialToolsrv(socketPath, "") if err != nil { t.Fatalf("Dial with empty secret failed: %v", err) } @@ -231,7 +230,7 @@ func TestIntegration_BasicOperations(t *testing.T) { socketPath, cleanup := startTestServer(t) defer cleanup() - conn, err := client.DialToolsrv(socketPath, "test-secret-ops") + conn, err := DialToolsrv(socketPath, "test-secret-ops") if err != nil { t.Fatalf("Dial failed: %v", err) } @@ -269,7 +268,7 @@ func TestIntegration_ToolExecution(t *testing.T) { socketPath, cleanup := startTestServer(t) defer cleanup() - conn, err := client.DialToolsrv(socketPath, "test-secret-exec") + conn, err := DialToolsrv(socketPath, "test-secret-exec") if err != nil { t.Fatalf("Dial failed: %v", err) } @@ -337,7 +336,7 @@ func TestIntegration_MultilineContent(t *testing.T) { socketPath, cleanup := startTestServer(t) defer cleanup() - conn, err := client.DialToolsrv(socketPath, "test-secret-multiline") + conn, err := DialToolsrv(socketPath, "test-secret-multiline") if err != nil { t.Fatalf("Dial failed: %v", err) } @@ -391,7 +390,7 @@ func TestIntegration_ToolExecutionError(t *testing.T) { socketPath, cleanup := startTestServer(t) defer cleanup() - conn, err := client.DialToolsrv(socketPath, "test-secret-exec-err") + conn, err := DialToolsrv(socketPath, "test-secret-exec-err") if err != nil { t.Fatalf("Dial failed: %v", err) } @@ -438,7 +437,7 @@ func TestIntegration_ConcurrentConnections(t *testing.T) { secret := "test-secret-concurrent" // Establish the secret - conn0, err := client.DialToolsrv(socketPath, secret) + conn0, err := DialToolsrv(socketPath, secret) if err != nil { t.Fatalf("initial Dial failed: %v", err) } @@ -447,11 +446,11 @@ func TestIntegration_ConcurrentConnections(t *testing.T) { // Open multiple concurrent connections const numConns = 5 - conns := make([]*client.ToolsrvConn, numConns) + conns := make([]*ToolsrvConn, numConns) errors := make([]error, numConns) for i := 0; i < numConns; i++ { - conns[i], errors[i] = client.DialToolsrv(socketPath, secret) + conns[i], errors[i] = DialToolsrv(socketPath, secret) } // Check all succeeded with same token @@ -480,7 +479,7 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) { socketPath1, cleanup1 := startTestServer(t) secret1 := "secret-for-first-server" - conn1, err := client.DialToolsrv(socketPath1, secret1) + conn1, err := DialToolsrv(socketPath1, secret1) if err != nil { cleanup1() t.Fatalf("first Dial failed: %v", err) @@ -497,7 +496,7 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) { // New server should accept a new secret (any secret, since it's fresh) secret2 := "secret-for-second-server" - conn2, err := client.DialToolsrv(socketPath2, secret2) + conn2, err := DialToolsrv(socketPath2, secret2) if err != nil { t.Fatalf("second Dial failed: %v", err) } @@ -510,7 +509,7 @@ func TestIntegration_ProcessKeeperRespawn(t *testing.T) { } // Old secret should NOT work on new server - conn3, err := client.DialToolsrv(socketPath2, secret1) + conn3, err := DialToolsrv(socketPath2, secret1) if err == nil { conn3.Close() t.Error("old secret should not work on new server") @@ -531,7 +530,7 @@ func TestIntegration_ToolCancellation(t *testing.T) { socketPath, cleanup := startTestServer(t) defer cleanup() - conn, err := client.DialToolsrv(socketPath, "test-secret-cancel") + conn, err := DialToolsrv(socketPath, "test-secret-cancel") if err != nil { t.Fatalf("Dial failed: %v", err) } @@ -597,7 +596,7 @@ func TestIntegration_ToolCancellation_ContextPropagation(t *testing.T) { socketPath, cleanup := startTestServer(t) defer cleanup() - conn, err := client.DialToolsrv(socketPath, "test-secret-cancel-prop") + conn, err := DialToolsrv(socketPath, "test-secret-cancel-prop") if err != nil { t.Fatalf("Dial failed: %v", err) } diff --git a/justfile b/justfile index 7656a0b..4a2d07a 100644 --- a/justfile +++ b/justfile @@ -275,7 +275,7 @@ test-core: test-9p: @echo === Testing ollie-9p === - go test ./client/... + go test ./cmd/ollie-9p/... ./kde/lib9p/... test-remote: @echo === Testing toolsrv === diff --git a/kde/lib9p/libollie9p.so b/kde/lib9p/libollie9p.so index 9507f8e..b4f6f02 100644 Binary files a/kde/lib9p/libollie9p.so and b/kde/lib9p/libollie9p.so differ