diff --git a/agent/runtime.go b/agent/runtime.go index 131ecef..ef717ca 100644 --- a/agent/runtime.go +++ b/agent/runtime.go @@ -174,6 +174,7 @@ func BuildRuntime(cfg *AgentConfig, srv *toolsrv.Conn, cwd string, env []string, genParams = cfg.GenerationParams maxSteps = cfg.MaxSteps } + genParams.CWD = cwd exec := func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) { if srv == nil { diff --git a/backend/backend.go b/backend/backend.go index a74e711..675fe76 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -140,6 +140,10 @@ type GenerationParams struct { IncludeReasoning *bool `json:"includeReasoning,omitempty"` ResponseFormat string `json:"responseFormat,omitempty"` Stop []string `json:"stop,omitempty"` + + // Environment state for backends that support it (e.g., Kiro). + // These are passed through to the backend API but not used for sampling. + CWD string `json:"-"` // Current working directory } // Backend is the interface all LLM providers must implement. diff --git a/backend/kiro.go b/backend/kiro.go index 3a1e07d..f2380bf 100644 --- a/backend/kiro.go +++ b/backend/kiro.go @@ -315,7 +315,7 @@ func buildKiroRequest(model string, messages []Message, tools []Tool, profileARN if err != nil { return nil, err } - current = kiroChatMessage{UserInputMessage: encodeKiroUserMessage(messages[len(messages)-1], model, encodedTools, nil, true)} + current = kiroChatMessage{UserInputMessage: encodeKiroUserMessage(messages[len(messages)-1], model, encodedTools, nil, params.CWD)} case "tool": // Find the last non-tool message. @@ -328,7 +328,7 @@ func buildKiroRequest(model string, messages []Message, tools []Tool, profileARN return nil, err } toolResults, images := encodeKiroToolResults(messages[cutoff+1:]) - current = kiroChatMessage{UserInputMessage: emptyKiroUserMessage(model, encodedTools, toolResults, images, true)} + current = kiroChatMessage{UserInputMessage: emptyKiroUserMessage(model, encodedTools, toolResults, images, params.CWD)} default: return nil, fmt.Errorf("last message role %q is not supported (expected user, system, or tool)", lastRole) @@ -363,7 +363,7 @@ func encodeKiroHistory(messages []Message) ([]kiroChatMessage, error) { m := messages[i] switch strings.ToLower(strings.TrimSpace(m.Role)) { case "user", "system": - out = append(out, kiroChatMessage{UserInputMessage: encodeKiroUserMessage(m, "", nil, nil, false)}) + out = append(out, kiroChatMessage{UserInputMessage: encodeKiroUserMessage(m, "", nil, nil, "")}) case "assistant": out = append(out, kiroChatMessage{AssistantResponseMessage: encodeKiroAssistantMessage(m)}) case "tool": @@ -373,13 +373,13 @@ func encodeKiroHistory(messages []Message) ([]kiroChatMessage, error) { i++ } results, imgs := encodeKiroToolResults(messages[start : i+1]) - out = append(out, kiroChatMessage{UserInputMessage: emptyKiroUserMessage("", nil, results, imgs, false)}) + out = append(out, kiroChatMessage{UserInputMessage: emptyKiroUserMessage("", nil, results, imgs, "")}) } } return out, nil } -func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResults []kiroToolResult, includeEnvState bool) *kiroUserInputMessage { +func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResults []kiroToolResult, cwd string) *kiroUserInputMessage { msg := &kiroUserInputMessage{ Content: kiroExtractTextContent(m), Origin: kiroDefaultOrigin, @@ -393,8 +393,8 @@ func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResu if len(toolResults) > 0 { ctx.ToolResults = toolResults } - if includeEnvState { - ctx.EnvState = kiroCurrentEnvState() + if cwd != "" { + ctx.EnvState = kiroCurrentEnvState(cwd) } if len(ctx.Tools) > 0 || len(ctx.ToolResults) > 0 || ctx.EnvState != nil { msg.UserInputMessageContext = ctx @@ -402,7 +402,7 @@ func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResu return msg } -func emptyKiroUserMessage(modelID string, tools []kiroTool, toolResults []kiroToolResult, images []kiroImageBlock, includeEnvState bool) *kiroUserInputMessage { +func emptyKiroUserMessage(modelID string, tools []kiroTool, toolResults []kiroToolResult, images []kiroImageBlock, cwd string) *kiroUserInputMessage { msg := &kiroUserInputMessage{ Content: "", Origin: kiroDefaultOrigin, @@ -416,8 +416,8 @@ func emptyKiroUserMessage(modelID string, tools []kiroTool, toolResults []kiroTo if len(toolResults) > 0 { ctx.ToolResults = toolResults } - if includeEnvState { - ctx.EnvState = kiroCurrentEnvState() + if cwd != "" { + ctx.EnvState = kiroCurrentEnvState(cwd) } if len(ctx.Tools) > 0 || len(ctx.ToolResults) > 0 || ctx.EnvState != nil { msg.UserInputMessageContext = ctx @@ -564,10 +564,10 @@ func kiroExtractImages(m Message) []kiroImageBlock { return images } -func kiroCurrentEnvState() *kiroEnvState { +func kiroCurrentEnvState(cwd string) *kiroEnvState { return &kiroEnvState{ OperatingSystem: runtime.GOOS, - CurrentWorkingDirectory: "/", + CurrentWorkingDirectory: cwd, } } diff --git a/data/scripts/o b/data/scripts/o index b4f316e..145efed 100755 --- a/data/scripts/o +++ b/data/scripts/o @@ -12,6 +12,7 @@ # read Read a file (-l to loop) # write [data] Write to a file (args or stdin) # ls [path] List namespace entries +# new [agent] [cwd] Create a new session or agent # ctl Send a control command (stop, compact, kill, ...) # prompt Interactive REPL for submitting prompts (requires agent) # chatstream Stream chat output (requires agent) @@ -280,6 +281,13 @@ cmd_prompt() { fi local path="session/$s/agent/$a" + # If stdin is not a terminal, read it all and send as a single prompt + if [[ ! -t 0 ]]; then + cat | ollie-9p write "$path/prompt" + return + fi + + # Interactive REPL mode echo "[$s/$a]" echo "Type . on a blank line to send, Ctrl+D to exit." echo "/ prefix runs ctl: /stop, /compact, /model X, /inject X" @@ -386,6 +394,30 @@ cmd_env() { fi } +cmd_new() { + if [[ $# -lt 1 ]]; then + echo "usage: o new [agent] [cwd]" >&2 + echo " o new myproject Create a new session named 'myproject'" >&2 + echo " o new myproject coding /path Create agent 'coding' in session 'myproject'" >&2 + exit 1 + fi + + local session_name="$1" + shift + + if [[ $# -eq 0 ]]; then + # Create new session + printf 'name=%s\ncwd=%s\n' "$session_name" "$PWD" | ollie-9p write "session/new" + echo "created session: $session_name" + else + # Create new agent in existing session + local agent_name="$1" + local agent_cwd="${2:-$PWD}" + printf 'name=%s\ncwd=%s\n' "$agent_name" "$agent_cwd" | ollie-9p write "session/$session_name/agent/new" + echo "created agent: $session_name/$agent_name" + fi +} + cmd_tui() { local tmux_session="${session:-}" local s a @@ -483,6 +515,9 @@ Commands: write [data] Write to a file (args or stdin) ls [path] List namespace entries + new Create a new session + new [cwd] Create an agent in a session + generate One-shot generation (no session needed) ctl [args] Send control command (rdwr: gets response) @@ -506,6 +541,8 @@ Examples: o read -l statewait # block-loop on state changes o read chat # stream chat (filtered text) o read chat.raw # stream chat (full markup) + o new myproject # create session 'myproject' in current dir + o new myproject coding # create agent 'coding' in 'myproject' o generate "explain monads" # one-shot, no session o ctl stop # stop agent (uses $session/$agent) o ctl kill mysession # kill session by name @@ -529,6 +566,7 @@ case "$subcmd" in ctl) cmd_ctl "$@" ;; prompt) cmd_prompt "$@" ;; log) cmd_log "$@" ;; + new) cmd_new "$@" ;; tui) cmd_tui "$@" ;; env) cmd_env "$@" ;; help|-h|--help) cmd_help ;;