Skip to content

Commit 3243523

Browse files
committed
Support steering for remote runtimes
Add SteerSession to the RemoteClient interface and implement it on the HTTP Client (POST /sessions/:id/steer). RemoteRuntime.Steer() delegates to the remote server so the TUI works identically regardless of whether the runtime is local or remote. App.Steer() now tries GetLocalRuntime first, then falls back to the Steerer interface so both PersistentRuntime and RemoteRuntime are handled.
1 parent 717c63a commit 3243523

4 files changed

Lines changed: 43 additions & 0 deletions

File tree

pkg/app/app.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,27 @@ func (a *App) SubscribeWith(ctx context.Context, send func(tea.Msg)) {
532532
}
533533
}
534534

535+
// Steerer is implemented by runtimes that support mid-turn message injection.
536+
type Steerer interface {
537+
Steer(msg runtime.SteeredMessage) bool
538+
}
539+
540+
// Steer enqueues a user message for mid-turn injection into the running
541+
// agent loop. Works with both local runtimes (via the SteerQueue) and
542+
// remote runtimes (via POST /sessions/:id/steer). Returns false if
543+
// steering is not supported by the runtime or the queue is full.
544+
func (a *App) Steer(msg runtime.SteeredMessage) bool {
545+
// Try unwrapping PersistentRuntime → LocalRuntime first
546+
if lr := runtime.GetLocalRuntime(a.runtime); lr != nil {
547+
return lr.Steer(msg)
548+
}
549+
// Try the Steerer interface (e.g. RemoteRuntime)
550+
if s, ok := a.runtime.(Steerer); ok {
551+
return s.Steer(msg)
552+
}
553+
return false
554+
}
555+
535556
// Resume resumes the runtime with the given confirmation request
536557
func (a *App) Resume(req runtime.ResumeRequest) {
537558
a.runtime.Resume(context.Background(), req)

pkg/runtime/client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ func (c *Client) ResumeSession(ctx context.Context, id, confirmation, reason, to
266266
return c.doRequest(ctx, http.MethodPost, "/api/sessions/"+id+"/resume", req, nil)
267267
}
268268

269+
// SteerSession injects user messages into a running session mid-turn.
270+
func (c *Client) SteerSession(ctx context.Context, sessionID string, messages []api.Message) error {
271+
req := api.SteerSessionRequest{Messages: messages}
272+
return c.doRequest(ctx, http.MethodPost, "/api/sessions/"+sessionID+"/steer", req, nil)
273+
}
274+
269275
// DeleteSession deletes a session by ID
270276
func (c *Client) DeleteSession(ctx context.Context, id string) error {
271277
return c.doRequest(ctx, "DELETE", "/api/sessions/"+id, nil, nil)

pkg/runtime/remote_client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ type RemoteClient interface {
3030
// RunAgentWithAgentName executes an agent with a specific agent name
3131
RunAgentWithAgentName(ctx context.Context, sessionID, agent, agentName string, messages []api.Message) (<-chan Event, error)
3232

33+
// SteerSession injects user messages into a running session mid-turn
34+
SteerSession(ctx context.Context, sessionID string, messages []api.Message) error
35+
3336
// UpdateSessionTitle updates the title of a session
3437
UpdateSessionTitle(ctx context.Context, sessionID, title string) error
3538

pkg/runtime/remote_runtime.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,19 @@ func (r *RemoteRuntime) Run(ctx context.Context, sess *session.Session) ([]sessi
211211
return sess.GetAllMessages(), nil
212212
}
213213

214+
// Steer enqueues a user message for mid-turn injection into the running
215+
// agent loop on the remote server. Returns false if the session is not
216+
// active or the steer queue is full.
217+
func (r *RemoteRuntime) Steer(msg SteeredMessage) bool {
218+
if r.sessionID == "" {
219+
return false
220+
}
221+
err := r.client.SteerSession(context.Background(), r.sessionID, []api.Message{
222+
{Content: msg.Content, MultiContent: msg.MultiContent},
223+
})
224+
return err == nil
225+
}
226+
214227
// Resume allows resuming execution after user confirmation
215228
func (r *RemoteRuntime) Resume(ctx context.Context, req ResumeRequest) {
216229
slog.Debug("Resuming remote runtime", "agent", r.currentAgent, "type", req.Type, "reason", req.Reason, "tool_name", req.ToolName, "session_id", r.sessionID)

0 commit comments

Comments
 (0)