remote: fetch host_info, inject PWD for remote prompt resolution

prompt_resolver: when cmd.Dir cannot be set (remote CWD does not exist
locally), inject PWD=cwd into subprocess env so envsubst in prime sees
the correct working directory.

remote.Server: fetch host_info RPC after connect, expose HostInfo struct
with platform, arch, is_git_repo for the session to pass to prompts.
This commit is contained in:
ollie 2026-07-19 20:17:32 +02:00
parent 414220c2e5
commit a53d5b9618
2 changed files with 57 additions and 3 deletions

View File

@ -46,11 +46,13 @@ func resolveExecPrompt(cmds []string, cwd string, env []string) (string, error)
if cwd != "" {
if info, err := os.Stat(cwd); err == nil && info.IsDir() {
cmd.Dir = cwd
} else {
// CWD doesn't exist locally (remote session). Inject PWD so
// scripts that use ${PWD} via envsubst see the correct value.
env = append(env, "PWD="+cwd)
}
}
if len(env) > 0 {
cmd.Env = append(os.Environ(), env...)
}
cmd.Env = append(os.Environ(), env...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
@ -101,11 +103,17 @@ func resolveStringPrompt(prompt, cwd string, env []string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", cmdStr)
var cmdEnv []string
if cwd != "" {
if info, err := os.Stat(cwd); err == nil && info.IsDir() {
cmd.Dir = cwd
} else {
cmdEnv = append(cmdEnv, "PWD="+cwd)
}
}
if len(cmdEnv) > 0 {
cmd.Env = append(os.Environ(), cmdEnv...)
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

View File

@ -31,6 +31,13 @@ import (
//go:embed bootstrap.sh
var bootstrapTemplate string
// HostInfo holds environment details from the remote host.
type HostInfo struct {
Platform string `json:"platform"`
Arch string `json:"arch"`
IsGitRepo bool `json:"is_git_repo"`
}
// Server implements tools.Server by forwarding calls to a remote
// ollie-remote process over SSH.
type Server struct {
@ -41,6 +48,7 @@ type Server struct {
enc *json.Encoder
dec *json.Decoder
nextID atomic.Int64
Info HostInfo
}
// Config holds the parameters for connecting to a remote host.
@ -179,6 +187,15 @@ func Dial(ctx context.Context, cfg Config) (*Server, error) {
return nil, fmt.Errorf("remote ping failed: %w", err)
}
// Fetch remote host info for prompt resolution.
// FIXME: This duplicates information that prime scripts also try to detect
// locally (platform, is_git_repo). Eventually unify so prompt resolution
// always uses these env vars rather than running local checks against a
// non-existent remote CWD.
if info, err := s.fetchHostInfo(ctx); err == nil {
s.Info = info
}
return s, nil
}
@ -300,6 +317,35 @@ func (s *Server) CallTool(ctx context.Context, tool string, args json.RawMessage
return resp.Result, nil
}
// fetchHostInfo retrieves environment details from the remote host.
func (s *Server) fetchHostInfo(ctx context.Context) (HostInfo, error) {
id := s.nextID.Add(1)
req := rpcRequest{
JSONRPC: "2.0",
ID: id,
Method: "host_info",
}
s.mu.Lock()
defer s.mu.Unlock()
if err := s.enc.Encode(req); err != nil {
return HostInfo{}, err
}
var resp rpcResponse
if err := s.dec.Decode(&resp); err != nil {
return HostInfo{}, err
}
if resp.Error != nil {
return HostInfo{}, fmt.Errorf("%s", resp.Error.Message)
}
var info HostInfo
if err := json.Unmarshal(resp.Result, &info); err != nil {
return HostInfo{}, err
}
return info, nil
}
// ping verifies the remote server is responsive.
func (s *Server) ping(ctx context.Context) error {
id := s.nextID.Add(1)