From 9e370804a207881c0a69a2ad11b563c1f296d442 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 12 Nov 2025 08:50:20 +0530 Subject: [PATCH 1/7] feat: improved initial prompt --- lib/httpapi/server.go | 12 +- lib/msgfmt/initialization_detection.go | 59 ++ lib/msgfmt/initialization_detection_test.go | 56 ++ .../initialization/aider/not_ready/msg.txt | 8 + .../initialization/aider/ready/msg.txt | 9 + .../initialization/amazonq/not_ready/msg.txt | 12 + .../initialization/amazonq/ready/msg.txt | 14 + .../initialization/amp/not_ready/msg.txt | 22 + .../testdata/initialization/amp/ready/msg.txt | 25 + .../initialization/auggie/not_ready/msg.txt | 12 + .../initialization/auggie/ready/msg.txt | 19 + .../initialization/claude/not_ready/msg.txt | 14 + .../initialization/claude/ready/msg.txt | 19 + .../initialization/codex/not_ready/msg.txt | 8 + .../initialization/codex/ready/msg.txt | 11 + .../initialization/copilot/not_ready/msg.txt | 11 + .../initialization/copilot/ready/msg.txt | 15 + .../initialization/cursor/not_ready/msg.txt | 11 + .../initialization/cursor/ready/msg.txt | 19 + .../initialization/gemini/not_ready/msg.txt | 7 + .../initialization/gemini/ready/msg.txt | 13 + .../initialization/goose/not_ready/msg.txt | 6 + .../initialization/goose/ready/msg.txt | 8 + .../initialization/opencode/not_ready/msg.txt | 12 + .../initialization/opencode/ready/msg.txt | 604 ++++++++++++++++++ lib/screentracker/conversation.go | 13 + 26 files changed, 1016 insertions(+), 3 deletions(-) create mode 100644 lib/msgfmt/initialization_detection.go create mode 100644 lib/msgfmt/initialization_detection_test.go create mode 100644 lib/msgfmt/testdata/initialization/aider/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/aider/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/amazonq/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/amazonq/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/amp/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/amp/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/auggie/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/auggie/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/claude/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/claude/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/codex/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/codex/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/copilot/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/copilot/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/cursor/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/cursor/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/gemini/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/gemini/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/goose/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/goose/ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/opencode/not_ready/msg.txt create mode 100644 lib/msgfmt/testdata/initialization/opencode/ready/msg.txt diff --git a/lib/httpapi/server.go b/lib/httpapi/server.go index b97f8da..0af665c 100644 --- a/lib/httpapi/server.go +++ b/lib/httpapi/server.go @@ -228,15 +228,21 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) { formatMessage := func(message string, userInput string) string { return mf.FormatAgentMessage(config.AgentType, message, userInput) } + + isAgentReadyForInitialPrompt := func(message string) bool { + return mf.IsAgentReadyForInitialPrompt(config.AgentType, message) + } + conversation := st.NewConversation(ctx, st.ConversationConfig{ AgentType: config.AgentType, AgentIO: config.Process, GetTime: func() time.Time { return time.Now() }, - SnapshotInterval: snapshotInterval, - ScreenStabilityLength: 2 * time.Second, - FormatMessage: formatMessage, + SnapshotInterval: snapshotInterval, + ScreenStabilityLength: 2 * time.Second, + FormatMessage: formatMessage, + IsAgentReadyForInitialPrompt: isAgentReadyForInitialPrompt, }, config.InitialPrompt) emitter := NewEventEmitter(1024) diff --git a/lib/msgfmt/initialization_detection.go b/lib/msgfmt/initialization_detection.go new file mode 100644 index 0000000..8c919c2 --- /dev/null +++ b/lib/msgfmt/initialization_detection.go @@ -0,0 +1,59 @@ +package msgfmt + +func IsAgentReadyForInitialPrompt(agentType AgentType, message string) bool { + switch agentType { + case AgentTypeClaude: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeGoose: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeAider: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeCodex: + return isCodexAgentReadyForInitialPrompt(message) + case AgentTypeGemini: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeCopilot: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeAmp: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeCursor: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeAuggie: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeAmazonQ: + return isGenericAgentReadyForInitialPrompt(message) + case AgentTypeOpencode: + return isOpencodeAgentReadyForInitialPrompt(message) + case AgentTypeCustom: + return isGenericAgentReadyForInitialPrompt(message) + default: + return true + } +} + +func isGenericAgentReadyForInitialPrompt(message string) bool { + message = trimEmptyLines(message) + messageWithoutInputBox := removeMessageBox(message) + if len(messageWithoutInputBox) == len(message) { + return false + } + return true +} + +func isOpencodeAgentReadyForInitialPrompt(message string) bool { + message = trimEmptyLines(message) + messageWithoutInputBox := removeOpencodeMessageBox(message) + if len(messageWithoutInputBox) == len(message) { + return false + } + return true +} + +func isCodexAgentReadyForInitialPrompt(message string) bool { + message = trimEmptyLines(message) + messageWithoutInputBox := removeCodexInputBox(message) + if len(messageWithoutInputBox) == len(message) { + return false + } + return true +} diff --git a/lib/msgfmt/initialization_detection_test.go b/lib/msgfmt/initialization_detection_test.go new file mode 100644 index 0000000..f58db24 --- /dev/null +++ b/lib/msgfmt/initialization_detection_test.go @@ -0,0 +1,56 @@ +package msgfmt + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsAgentReadyForInitialPrompt(t *testing.T) { + dir := "testdata/initialization" + agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeCopilot, AgentTypeAmp, AgentTypeCodex, AgentTypeCursor, AgentTypeAuggie, AgentTypeAmazonQ, AgentTypeOpencode, AgentTypeCustom} + for _, agentType := range agentTypes { + t.Run(string(agentType), func(t *testing.T) { + t.Run("ready", func(t *testing.T) { + cases, err := testdataDir.ReadDir(path.Join(dir, string(agentType), "ready")) + if err != nil { + t.Skipf("failed to read ready cases for agent type %s: %s", agentType, err) + } + if len(cases) == 0 { + t.Skipf("no ready cases found for agent type %s", agentType) + } + for _, c := range cases { + if c.IsDir() { + continue + } + t.Run(c.Name(), func(t *testing.T) { + msg, err := testdataDir.ReadFile(path.Join(dir, string(agentType), "ready", c.Name())) + assert.NoError(t, err) + assert.True(t, IsAgentReadyForInitialPrompt(agentType, string(msg)), "Expected agent to be ready for message:\n%s", string(msg)) + }) + } + }) + + t.Run("not_ready", func(t *testing.T) { + cases, err := testdataDir.ReadDir(path.Join(dir, string(agentType), "not_ready")) + if err != nil { + t.Skipf("failed to read not_ready cases for agent type %s: %s", agentType, err) + } + if len(cases) == 0 { + t.Skipf("no not_ready cases found for agent type %s", agentType) + } + for _, c := range cases { + if c.IsDir() { + continue + } + t.Run(c.Name(), func(t *testing.T) { + msg, err := testdataDir.ReadFile(path.Join(dir, string(agentType), "not_ready", c.Name())) + assert.NoError(t, err) + assert.False(t, IsAgentReadyForInitialPrompt(agentType, string(msg)), "Expected agent to not be ready for message:\n%s", string(msg)) + }) + } + }) + }) + } +} diff --git a/lib/msgfmt/testdata/initialization/aider/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/aider/not_ready/msg.txt new file mode 100644 index 0000000..be53715 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/aider/not_ready/msg.txt @@ -0,0 +1,8 @@ +──────────────────────────────────────────────────────────────────────────────── +Aider v0.81.1 +Main model: anthropic/claude-3-7-sonnet-20250219 with diff edit format, infinite +output +Weak model: anthropic/claude-3-5-haiku-20241022 +Git repo: .git with 47 files +Repo-map: using 4096 tokens, auto refresh +──────────────────────────────────────────────────────────────────────────────── diff --git a/lib/msgfmt/testdata/initialization/aider/ready/msg.txt b/lib/msgfmt/testdata/initialization/aider/ready/msg.txt new file mode 100644 index 0000000..e963a1b --- /dev/null +++ b/lib/msgfmt/testdata/initialization/aider/ready/msg.txt @@ -0,0 +1,9 @@ +──────────────────────────────────────────────────────────────────────────────── +Aider v0.81.1 +Main model: anthropic/claude-3-7-sonnet-20250219 with diff edit format, infinite +output +Weak model: anthropic/claude-3-5-haiku-20241022 +Git repo: .git with 47 files +Repo-map: using 4096 tokens, auto refresh +──────────────────────────────────────────────────────────────────────────────── +> \ No newline at end of file diff --git a/lib/msgfmt/testdata/initialization/amazonq/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/amazonq/not_ready/msg.txt new file mode 100644 index 0000000..dfcc1ff --- /dev/null +++ b/lib/msgfmt/testdata/initialization/amazonq/not_ready/msg.txt @@ -0,0 +1,12 @@ +✓ coder loaded in 0.03 s + +Welcome to Amazon Q! + +💡 Run /prompts to learn how to build & run repeatable workflows + +/help all commands +ctrl + j new lines +ctrl + s fuzzy search + + +🤖 You are chatting with claude-sonnet-4 diff --git a/lib/msgfmt/testdata/initialization/amazonq/ready/msg.txt b/lib/msgfmt/testdata/initialization/amazonq/ready/msg.txt new file mode 100644 index 0000000..4f43700 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/amazonq/ready/msg.txt @@ -0,0 +1,14 @@ +✓ coder loaded in 0.03 s + +Welcome to Amazon Q! + +💡 Run /prompts to learn how to build & run repeatable workflows + +/help all commands +ctrl + j new lines +ctrl + s fuzzy search + + +🤖 You are chatting with claude-sonnet-4 + +> \ No newline at end of file diff --git a/lib/msgfmt/testdata/initialization/amp/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/amp/not_ready/msg.txt new file mode 100644 index 0000000..61f23dc --- /dev/null +++ b/lib/msgfmt/testdata/initialization/amp/not_ready/msg.txt @@ -0,0 +1,22 @@ + + + ............ + ..:::-------::::... + ..::--=========---:::... Welcome to Amp + .::-===++++++++===---:::.. + ..:--==+++******+++===--:::... + .:--=+++**********++===--:::... Type / to use slash commands + .::-==++***#####****++==---:::.. Type @ to mention files + .:--=++****#####****++===---::... Ctrl+C to exit + .:--=+++****###****+++===---:::.. + .:--==+++*********++++===---:::.. + ..:--==++++*****++++====----:::.. /help for more + .::--===+++++++++====-----:::... + .::---===========------::::... + ..:::-------------:::::::... amp -x "Run the linter and fix the errors" + ...::::::::::::::::..... + .................. + + + + diff --git a/lib/msgfmt/testdata/initialization/amp/ready/msg.txt b/lib/msgfmt/testdata/initialization/amp/ready/msg.txt new file mode 100644 index 0000000..a3be16b --- /dev/null +++ b/lib/msgfmt/testdata/initialization/amp/ready/msg.txt @@ -0,0 +1,25 @@ + + + ............ + ..:::-------::::... + ..::--=========---:::... Welcome to Amp + .::-===++++++++===---:::.. + ..:--==+++******+++===--:::... + .:--=+++**********++===--:::... Type / to use slash commands + .::-==++***#####****++==---:::.. Type @ to mention files + .:--=++****#####****++===---::... Ctrl+C to exit + .:--=+++****###****+++===---:::.. + .:--==+++*********++++===---:::.. + ..:--==++++*****++++====----:::.. /help for more + .::--===+++++++++====-----:::... + .::---===========------::::... + ..:::-------------:::::::... amp -x "Run the linter and fix the errors" + ...::::::::::::::::..... + .................. + + + + +─────────────── +> +─────────────── diff --git a/lib/msgfmt/testdata/initialization/auggie/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/auggie/not_ready/msg.txt new file mode 100644 index 0000000..902ff9e --- /dev/null +++ b/lib/msgfmt/testdata/initialization/auggie/not_ready/msg.txt @@ -0,0 +1,12 @@ + + + Getting started with Auggie by Augment Code + 1. You can ask questions, edit files, or run commands + 2. Your workspace is automatically indexed for best results + 3. Commands will run automatically + + + 💡 For automation, use 'auggie --print "your task"' + + + diff --git a/lib/msgfmt/testdata/initialization/auggie/ready/msg.txt b/lib/msgfmt/testdata/initialization/auggie/ready/msg.txt new file mode 100644 index 0000000..b5751da --- /dev/null +++ b/lib/msgfmt/testdata/initialization/auggie/ready/msg.txt @@ -0,0 +1,19 @@ + + + Getting started with Auggie by Augment Code + 1. You can ask questions, edit files, or run commands + 2. Your workspace is automatically indexed for best results + 3. Commands will run automatically + + + 💡 For automation, use 'auggie --print "your task"' + + + + +╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ › Try 'how do I log an error?' or type / for commands │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + Type / for commands • ↑↓ for history • Esc/Ctrl+C to interrupt ~/Documents/work/agentapi + + diff --git a/lib/msgfmt/testdata/initialization/claude/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/claude/not_ready/msg.txt new file mode 100644 index 0000000..9d39e34 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/claude/not_ready/msg.txt @@ -0,0 +1,14 @@ +╭────────────────────────────────────────────╮ +│ ✻ Welcome to Claude Code research preview! │ +│ │ +│ /help for help │ +│ │ +│ cwd: /Users/hugodutka/dev/agentapi │ +╰────────────────────────────────────────────╯ + + Tips for getting started: + + 1. Run /init to create a CLAUDE.md file with instructions for Claude + 2. Run /terminal-setup to set up terminal integration + 3. Use Claude to help with file analysis, editing, bash commands and git + 4. Be as specific as you would with another engineer for the best results diff --git a/lib/msgfmt/testdata/initialization/claude/ready/msg.txt b/lib/msgfmt/testdata/initialization/claude/ready/msg.txt new file mode 100644 index 0000000..d1a9bb0 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/claude/ready/msg.txt @@ -0,0 +1,19 @@ +╭────────────────────────────────────────────╮ +│ ✻ Welcome to Claude Code research preview! │ +│ │ +│ /help for help │ +│ │ +│ cwd: /Users/hugodutka/dev/agentapi │ +╰────────────────────────────────────────────╯ + + Tips for getting started: + + 1. Run /init to create a CLAUDE.md file with instructions for Claude + 2. Run /terminal-setup to set up terminal integration + 3. Use Claude to help with file analysis, editing, bash commands and git + 4. Be as specific as you would with another engineer for the best results + +╭──────────────────────────────────────────────────────────────────────────────╮ +│ > Try "refactor handler.go" │ +╰──────────────────────────────────────────────────────────────────────────────╯ + ? for shortcuts \ No newline at end of file diff --git a/lib/msgfmt/testdata/initialization/codex/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/codex/not_ready/msg.txt new file mode 100644 index 0000000..7a50888 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/codex/not_ready/msg.txt @@ -0,0 +1,8 @@ +>_ You are using OpenAI Codex in ~ + + To get started, describe a task or try one of these commands: + + /init - create an AGENTS.md file with instructions for Codex + /status - show current session configuration and token usage + /diff - show git diff (including untracked files) + /prompts - show example prompts diff --git a/lib/msgfmt/testdata/initialization/codex/ready/msg.txt b/lib/msgfmt/testdata/initialization/codex/ready/msg.txt new file mode 100644 index 0000000..76b1ef4 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/codex/ready/msg.txt @@ -0,0 +1,11 @@ +>_ You are using OpenAI Codex in ~ + + To get started, describe a task or try one of these commands: + + /init - create an AGENTS.md file with instructions for Codex + /status - show current session configuration and token usage + /diff - show git diff (including untracked files) + /prompts - show example prompts + +▌ Ask Codex to do anything + ⏎ send Shift+⏎ newline Ctrl+C quit \ No newline at end of file diff --git a/lib/msgfmt/testdata/initialization/copilot/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/copilot/not_ready/msg.txt new file mode 100644 index 0000000..c84773f --- /dev/null +++ b/lib/msgfmt/testdata/initialization/copilot/not_ready/msg.txt @@ -0,0 +1,11 @@ + Welcome to GitHub Copilot CLI + Version 0.0.328 · Commit 3755a93 + + Copilot can write, test and debug code right from your terminal. Describe a + task to get started or enter ? for help. Copilot uses AI, check for mistakes. + + ● Logged in with gh as user: 35C4n0r + + ● Connected to GitHub MCP Server + + ~/Documents/work/agentapi [⎇ feat-github-cli*] diff --git a/lib/msgfmt/testdata/initialization/copilot/ready/msg.txt b/lib/msgfmt/testdata/initialization/copilot/ready/msg.txt new file mode 100644 index 0000000..dee0c4d --- /dev/null +++ b/lib/msgfmt/testdata/initialization/copilot/ready/msg.txt @@ -0,0 +1,15 @@ + Welcome to GitHub Copilot CLI + Version 0.0.328 · Commit 3755a93 + + Copilot can write, test and debug code right from your terminal. Describe a + task to get started or enter ? for help. Copilot uses AI, check for mistakes. + + ● Logged in with gh as user: 35C4n0r + + ● Connected to GitHub MCP Server + + ~/Documents/work/agentapi [⎇ feat-github-cli*] + ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ > Enter @ to mention files or / for commands │ + ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + Ctrl+c Exit · Ctrl+r Expand all \ No newline at end of file diff --git a/lib/msgfmt/testdata/initialization/cursor/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/cursor/not_ready/msg.txt new file mode 100644 index 0000000..5857fbb --- /dev/null +++ b/lib/msgfmt/testdata/initialization/cursor/not_ready/msg.txt @@ -0,0 +1,11 @@ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ⬢ Welcome to Cursor Agent Beta │ + │ │ + │ Cursor Agent CLI is in beta. Security safeguards are still evolving. It can read, modify, and delete files, and execute shell commands you approve. Use at your own risk and only in trusted environments. │ + │ │ + │ Please read about our security at https://cursor.com/security. │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli diff --git a/lib/msgfmt/testdata/initialization/cursor/ready/msg.txt b/lib/msgfmt/testdata/initialization/cursor/ready/msg.txt new file mode 100644 index 0000000..c560cda --- /dev/null +++ b/lib/msgfmt/testdata/initialization/cursor/ready/msg.txt @@ -0,0 +1,19 @@ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ⬢ Welcome to Cursor Agent Beta │ + │ │ + │ Cursor Agent CLI is in beta. Security safeguards are still evolving. It can read, modify, and delete files, and execute shell commands you approve. Use at your own risk and only in trusted environments. │ + │ │ + │ Please read about our security at https://cursor.com/security. │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ → Plan, search, build anything │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + OpenAI GPT-5 + / for commands · @ for files + diff --git a/lib/msgfmt/testdata/initialization/gemini/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/gemini/not_ready/msg.txt new file mode 100644 index 0000000..375f12e --- /dev/null +++ b/lib/msgfmt/testdata/initialization/gemini/not_ready/msg.txt @@ -0,0 +1,7 @@ +Tips for getting started: +1. Ask questions, edit files, or run commands. +2. Be specific for the best results. +3. Create GEMINI.md files to customize your interactions with Gemini. +4. /help for more information. + + diff --git a/lib/msgfmt/testdata/initialization/gemini/ready/msg.txt b/lib/msgfmt/testdata/initialization/gemini/ready/msg.txt new file mode 100644 index 0000000..2cad627 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/gemini/ready/msg.txt @@ -0,0 +1,13 @@ +Tips for getting started: +1. Ask questions, edit files, or run commands. +2. Be specific for the best results. +3. Create GEMINI.md files to customize your interactions with Gemini. +4. /help for more information. + + + +╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ > Type your message or @path/to/file │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + +~/Documents/work/agentapi (feat-claude-cli*) no sandbox (see /docs) gemini-2.5-pro (100% context left) diff --git a/lib/msgfmt/testdata/initialization/goose/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/goose/not_ready/msg.txt new file mode 100644 index 0000000..7d49347 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/goose/not_ready/msg.txt @@ -0,0 +1,6 @@ +starting session | provider: anthropic model: claude-3-5-sonnet-latest + logging to /Users/hugodutka/.local/share/goose/sessions/20250409_131437.json +l + working directory: /Users/hugodutka/dev/agentapi + +Goose is running! Enter your instructions, or try asking what goose can do. diff --git a/lib/msgfmt/testdata/initialization/goose/ready/msg.txt b/lib/msgfmt/testdata/initialization/goose/ready/msg.txt new file mode 100644 index 0000000..196b210 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/goose/ready/msg.txt @@ -0,0 +1,8 @@ +starting session | provider: anthropic model: claude-3-5-sonnet-latest + logging to /Users/hugodutka/.local/share/goose/sessions/20250409_131437.json +l + working directory: /Users/hugodutka/dev/agentapi + +Goose is running! Enter your instructions, or try asking what goose can do. + +( O)> \ No newline at end of file diff --git a/lib/msgfmt/testdata/initialization/opencode/not_ready/msg.txt b/lib/msgfmt/testdata/initialization/opencode/not_ready/msg.txt new file mode 100644 index 0000000..21945ef --- /dev/null +++ b/lib/msgfmt/testdata/initialization/opencode/not_ready/msg.txt @@ -0,0 +1,12 @@ + █▀▀█ █▀▀█ █▀▀ █▀▀▄ █▀▀ █▀▀█ █▀▀▄ █▀▀ + █░░█ █░░█ █▀▀ █░░█ █░░ █░░█ █░░█ █▀▀ + ▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀ + v0.6.8 + + /new new session ctrl+x n + /help show help ctrl+x h + /share share session ctrl+x s + /models list models ctrl+x m + + + Grok Code is free for a limited time diff --git a/lib/msgfmt/testdata/initialization/opencode/ready/msg.txt b/lib/msgfmt/testdata/initialization/opencode/ready/msg.txt new file mode 100644 index 0000000..7b797b1 --- /dev/null +++ b/lib/msgfmt/testdata/initialization/opencode/ready/msg.txt @@ -0,0 +1,604 @@ + █▀▀█ █▀▀█ █▀▀ █▀▀▄ █▀▀ █▀▀█ █▀▀▄ █▀▀ + █░░█ █░░█ █▀▀ █░░█ █░░ █░░█ █░░█ █▀▀ + ▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀ + v0.6.8 + + /new new session ctrl+x n + /help show help ctrl+x h + /share share session ctrl+x s + /models list models ctrl+x m + + + Grok Code is free for a limited time + + + ┃ ┃ + ┃ > ┃ + ┃ ┃ + enter send + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + opencode v0.6.8 ~/Documents/work/agentapi tab ┃ BUILD AGENT \ No newline at end of file diff --git a/lib/screentracker/conversation.go b/lib/screentracker/conversation.go index 4617e8e..a6122a0 100644 --- a/lib/screentracker/conversation.go +++ b/lib/screentracker/conversation.go @@ -41,6 +41,8 @@ type ConversationConfig struct { // SkipSendMessageStatusCheck skips the check for whether the message can be sent. // This is used in tests SkipSendMessageStatusCheck bool + // IsAgentReadyForInitialPrompt detects whether the agent has initialized and is ready to accept the initial prompt + IsAgentReadyForInitialPrompt func(message string) bool } type ConversationRole string @@ -78,6 +80,8 @@ type Conversation struct { InitialPrompt string // InitialPromptSent keeps track if the InitialPrompt has been successfully sent to the agents InitialPromptSent bool + // AgentReadyForInitialPrompt keeps track if the agent is ready to accept the initial prompt + AgentReadyForInitialPrompt bool } type ConversationStatus string @@ -401,6 +405,15 @@ func (c *Conversation) statusInner() ConversationStatus { return ConversationStatusChanging } } + + if !c.InitialPromptSent && !c.AgentReadyForInitialPrompt { + if c.cfg.IsAgentReadyForInitialPrompt(snapshots[0].screen) { + c.AgentReadyForInitialPrompt = true + return ConversationStatusStable + } + return ConversationStatusChanging + } + return ConversationStatusStable } From 6a974dd7197b69be717439290ab2a310ffc02a55 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 12 Nov 2025 08:55:55 +0530 Subject: [PATCH 2/7] chore: rename file --- lib/msgfmt/{initialization_detection.go => agent_readiness.go} | 0 .../{initialization_detection_test.go => agent_readiness_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename lib/msgfmt/{initialization_detection.go => agent_readiness.go} (100%) rename lib/msgfmt/{initialization_detection_test.go => agent_readiness_test.go} (100%) diff --git a/lib/msgfmt/initialization_detection.go b/lib/msgfmt/agent_readiness.go similarity index 100% rename from lib/msgfmt/initialization_detection.go rename to lib/msgfmt/agent_readiness.go diff --git a/lib/msgfmt/initialization_detection_test.go b/lib/msgfmt/agent_readiness_test.go similarity index 100% rename from lib/msgfmt/initialization_detection_test.go rename to lib/msgfmt/agent_readiness_test.go From ff26f8c192cb805fdccfb9b037c03fddeccee02e Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 18 Nov 2025 08:39:20 +0530 Subject: [PATCH 3/7] feat: minor fixes --- lib/msgfmt/agent_readiness.go | 15 +- .../initialization/opencode/ready/msg.txt | 601 +----------------- lib/screentracker/conversation.go | 2 +- 3 files changed, 13 insertions(+), 605 deletions(-) diff --git a/lib/msgfmt/agent_readiness.go b/lib/msgfmt/agent_readiness.go index 8c919c2..c814685 100644 --- a/lib/msgfmt/agent_readiness.go +++ b/lib/msgfmt/agent_readiness.go @@ -34,26 +34,17 @@ func IsAgentReadyForInitialPrompt(agentType AgentType, message string) bool { func isGenericAgentReadyForInitialPrompt(message string) bool { message = trimEmptyLines(message) messageWithoutInputBox := removeMessageBox(message) - if len(messageWithoutInputBox) == len(message) { - return false - } - return true + return len(messageWithoutInputBox) != len(message) } func isOpencodeAgentReadyForInitialPrompt(message string) bool { message = trimEmptyLines(message) messageWithoutInputBox := removeOpencodeMessageBox(message) - if len(messageWithoutInputBox) == len(message) { - return false - } - return true + return len(messageWithoutInputBox) != len(message) } func isCodexAgentReadyForInitialPrompt(message string) bool { message = trimEmptyLines(message) messageWithoutInputBox := removeCodexInputBox(message) - if len(messageWithoutInputBox) == len(message) { - return false - } - return true + return len(messageWithoutInputBox) != len(message) } diff --git a/lib/msgfmt/testdata/initialization/opencode/ready/msg.txt b/lib/msgfmt/testdata/initialization/opencode/ready/msg.txt index 7b797b1..25ae98e 100644 --- a/lib/msgfmt/testdata/initialization/opencode/ready/msg.txt +++ b/lib/msgfmt/testdata/initialization/opencode/ready/msg.txt @@ -10,595 +10,12 @@ Grok Code is free for a limited time - - - ┃ ┃ - ┃ > ┃ - ┃ ┃ - enter send - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - opencode v0.6.8 ~/Documents/work/agentapi tab ┃ BUILD AGENT \ No newline at end of file + + + ┃ ┃ + ┃ > ┃ + ┃ ┃ + enter send + + + diff --git a/lib/screentracker/conversation.go b/lib/screentracker/conversation.go index a6122a0..d603300 100644 --- a/lib/screentracker/conversation.go +++ b/lib/screentracker/conversation.go @@ -407,7 +407,7 @@ func (c *Conversation) statusInner() ConversationStatus { } if !c.InitialPromptSent && !c.AgentReadyForInitialPrompt { - if c.cfg.IsAgentReadyForInitialPrompt(snapshots[0].screen) { + if len(snapshots) > 0 && c.cfg.IsAgentReadyForInitialPrompt(snapshots[len(snapshots)-1].screen) { c.AgentReadyForInitialPrompt = true return ConversationStatusStable } From dcd146fdb30515472bcad20b65177f0bbcebc4ab Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 18 Nov 2025 15:56:40 +0530 Subject: [PATCH 4/7] feat: add tests --- lib/screentracker/conversation_test.go | 137 +++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/lib/screentracker/conversation_test.go b/lib/screentracker/conversation_test.go index 92fe5ac..a6e0494 100644 --- a/lib/screentracker/conversation_test.go +++ b/lib/screentracker/conversation_test.go @@ -406,3 +406,140 @@ func TestPartsToString(t *testing.T) { ), ) } + +func TestInitialPromptReadiness(t *testing.T) { + now := time.Now() + changing := st.ConversationStatusChanging + stable := st.ConversationStatusStable + + t.Run("agent not ready - status remains changing", func(t *testing.T) { + cfg := st.ConversationConfig{ + GetTime: func() time.Time { return now }, + SnapshotInterval: 1 * time.Second, + ScreenStabilityLength: 2 * time.Second, + AgentIO: &testAgent{screen: "loading..."}, + IsAgentReadyForInitialPrompt: func(message string) bool { + return message == "ready" + }, + } + c := st.NewConversation(context.Background(), cfg, "initial prompt here") + + // Fill buffer with stable snapshots, but agent is not ready + c.AddSnapshot("loading...") + c.AddSnapshot("loading...") + c.AddSnapshot("loading...") + + // Even though screen is stable, status should be changing because agent is not ready + assert.Equal(t, changing, c.Status()) + assert.False(t, c.AgentReadyForInitialPrompt) + assert.False(t, c.InitialPromptSent) + }) + + t.Run("agent becomes ready - status changes to stable", func(t *testing.T) { + cfg := st.ConversationConfig{ + GetTime: func() time.Time { return now }, + SnapshotInterval: 1 * time.Second, + ScreenStabilityLength: 2 * time.Second, + AgentIO: &testAgent{screen: "loading..."}, + IsAgentReadyForInitialPrompt: func(message string) bool { + return message == "ready" + }, + } + c := st.NewConversation(context.Background(), cfg, "initial prompt here") + + // Agent not ready initially + c.AddSnapshot("loading...") + c.AddSnapshot("loading...") + c.AddSnapshot("loading...") + assert.Equal(t, changing, c.Status()) + + // Agent becomes ready + c.AddSnapshot("ready") + c.AddSnapshot("ready") + c.AddSnapshot("ready") + assert.Equal(t, stable, c.Status()) + assert.True(t, c.AgentReadyForInitialPrompt) + assert.False(t, c.InitialPromptSent) + }) + + t.Run("no initial prompt - normal status logic applies", func(t *testing.T) { + cfg := st.ConversationConfig{ + GetTime: func() time.Time { return now }, + SnapshotInterval: 1 * time.Second, + ScreenStabilityLength: 2 * time.Second, + AgentIO: &testAgent{screen: "loading..."}, + IsAgentReadyForInitialPrompt: func(message string) bool { + return false // Agent never ready + }, + } + // Empty initial prompt means no need to wait for readiness + c := st.NewConversation(context.Background(), cfg, "") + + c.AddSnapshot("loading...") + c.AddSnapshot("loading...") + c.AddSnapshot("loading...") + + // Status should be stable because no initial prompt to wait for + assert.Equal(t, stable, c.Status()) + assert.False(t, c.AgentReadyForInitialPrompt) + assert.True(t, c.InitialPromptSent) // Set to true when initial prompt is empty + }) + + t.Run("initial prompt sent - normal status logic applies", func(t *testing.T) { + cfg := st.ConversationConfig{ + GetTime: func() time.Time { return now }, + SnapshotInterval: 1 * time.Second, + ScreenStabilityLength: 2 * time.Second, + AgentIO: &testAgent{screen: "processing..."}, + IsAgentReadyForInitialPrompt: func(message string) bool { + return false // Agent never ready + }, + } + c := st.NewConversation(context.Background(), cfg, "initial prompt here") + // Manually mark as sent to simulate that initial prompt was already sent + c.InitialPromptSent = true + + c.AddSnapshot("processing...") + c.AddSnapshot("processing...") + c.AddSnapshot("processing...") + + // Status should be stable because initial prompt was already sent + assert.Equal(t, stable, c.Status()) + assert.False(t, c.AgentReadyForInitialPrompt) + assert.True(t, c.InitialPromptSent) + }) + + t.Run("agent readiness detected once - stays ready", func(t *testing.T) { + cfg := st.ConversationConfig{ + GetTime: func() time.Time { return now }, + SnapshotInterval: 1 * time.Second, + ScreenStabilityLength: 2 * time.Second, + AgentIO: &testAgent{screen: "ready"}, + IsAgentReadyForInitialPrompt: func(message string) bool { + return message == "ready" + }, + } + c := st.NewConversation(context.Background(), cfg, "initial prompt here") + + // Agent becomes ready + c.AddSnapshot("ready") + c.AddSnapshot("ready") + c.AddSnapshot("ready") + assert.Equal(t, stable, c.Status()) + assert.True(t, c.AgentReadyForInitialPrompt) + + // After agent is detected as ready, normal status logic applies + // Screen changes should cause changing status + c.AddSnapshot("changing") + assert.Equal(t, changing, c.Status()) + assert.True(t, c.AgentReadyForInitialPrompt) + + // Once screen stabilizes again, status should be stable + // AgentReadyForInitialPrompt remains true + c.AddSnapshot("stable now") + c.AddSnapshot("stable now") + c.AddSnapshot("stable now") + assert.Equal(t, stable, c.Status()) + assert.True(t, c.AgentReadyForInitialPrompt) + }) +} From aeae1b1a49415bd255c5fe328f4424f3aee05abe Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 19 Nov 2025 23:54:55 +0530 Subject: [PATCH 5/7] chore: refactor names --- lib/httpapi/server.go | 8 ++++---- lib/msgfmt/agent_readiness_test.go | 10 +++++----- lib/screentracker/conversation.go | 14 +++++++------- lib/screentracker/conversation_test.go | 26 +++++++++++++------------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/httpapi/server.go b/lib/httpapi/server.go index 5818e37..948b2f8 100644 --- a/lib/httpapi/server.go +++ b/lib/httpapi/server.go @@ -239,10 +239,10 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) { GetTime: func() time.Time { return time.Now() }, - SnapshotInterval: snapshotInterval, - ScreenStabilityLength: 2 * time.Second, - FormatMessage: formatMessage, - IsAgentReadyForInitialPrompt: isAgentReadyForInitialPrompt, + SnapshotInterval: snapshotInterval, + ScreenStabilityLength: 2 * time.Second, + FormatMessage: formatMessage, + ReadyForInitialPrompt: isAgentReadyForInitialPrompt, }, config.InitialPrompt) emitter := NewEventEmitter(1024) diff --git a/lib/msgfmt/agent_readiness_test.go b/lib/msgfmt/agent_readiness_test.go index f58db24..9576333 100644 --- a/lib/msgfmt/agent_readiness_test.go +++ b/lib/msgfmt/agent_readiness_test.go @@ -9,16 +9,16 @@ import ( func TestIsAgentReadyForInitialPrompt(t *testing.T) { dir := "testdata/initialization" - agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeCopilot, AgentTypeAmp, AgentTypeCodex, AgentTypeCursor, AgentTypeAuggie, AgentTypeAmazonQ, AgentTypeOpencode, AgentTypeCustom} + agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeCopilot, AgentTypeAmp, AgentTypeCodex, AgentTypeCursor, AgentTypeAuggie, AgentTypeAmazonQ, AgentTypeOpencode} for _, agentType := range agentTypes { t.Run(string(agentType), func(t *testing.T) { t.Run("ready", func(t *testing.T) { cases, err := testdataDir.ReadDir(path.Join(dir, string(agentType), "ready")) if err != nil { - t.Skipf("failed to read ready cases for agent type %s: %s", agentType, err) + t.Errorf("failed to read ready cases for agent type %s: %s", agentType, err) } if len(cases) == 0 { - t.Skipf("no ready cases found for agent type %s", agentType) + t.Errorf("no ready cases found for agent type %s", agentType) } for _, c := range cases { if c.IsDir() { @@ -35,10 +35,10 @@ func TestIsAgentReadyForInitialPrompt(t *testing.T) { t.Run("not_ready", func(t *testing.T) { cases, err := testdataDir.ReadDir(path.Join(dir, string(agentType), "not_ready")) if err != nil { - t.Skipf("failed to read not_ready cases for agent type %s: %s", agentType, err) + t.Errorf("failed to read not_ready cases for agent type %s: %s", agentType, err) } if len(cases) == 0 { - t.Skipf("no not_ready cases found for agent type %s", agentType) + t.Errorf("no not_ready cases found for agent type %s", agentType) } for _, c := range cases { if c.IsDir() { diff --git a/lib/screentracker/conversation.go b/lib/screentracker/conversation.go index d603300..043f440 100644 --- a/lib/screentracker/conversation.go +++ b/lib/screentracker/conversation.go @@ -41,8 +41,8 @@ type ConversationConfig struct { // SkipSendMessageStatusCheck skips the check for whether the message can be sent. // This is used in tests SkipSendMessageStatusCheck bool - // IsAgentReadyForInitialPrompt detects whether the agent has initialized and is ready to accept the initial prompt - IsAgentReadyForInitialPrompt func(message string) bool + // ReadyForInitialPrompt detects whether the agent has initialized and is ready to accept the initial prompt + ReadyForInitialPrompt func(message string) bool } type ConversationRole string @@ -80,8 +80,8 @@ type Conversation struct { InitialPrompt string // InitialPromptSent keeps track if the InitialPrompt has been successfully sent to the agents InitialPromptSent bool - // AgentReadyForInitialPrompt keeps track if the agent is ready to accept the initial prompt - AgentReadyForInitialPrompt bool + // ReadyForInitialPrompt keeps track if the agent is ready to accept the initial prompt + ReadyForInitialPrompt bool } type ConversationStatus string @@ -406,9 +406,9 @@ func (c *Conversation) statusInner() ConversationStatus { } } - if !c.InitialPromptSent && !c.AgentReadyForInitialPrompt { - if len(snapshots) > 0 && c.cfg.IsAgentReadyForInitialPrompt(snapshots[len(snapshots)-1].screen) { - c.AgentReadyForInitialPrompt = true + if !c.InitialPromptSent && !c.ReadyForInitialPrompt { + if len(snapshots) > 0 && c.cfg.ReadyForInitialPrompt(snapshots[len(snapshots)-1].screen) { + c.ReadyForInitialPrompt = true return ConversationStatusStable } return ConversationStatusChanging diff --git a/lib/screentracker/conversation_test.go b/lib/screentracker/conversation_test.go index a6e0494..852a0bd 100644 --- a/lib/screentracker/conversation_test.go +++ b/lib/screentracker/conversation_test.go @@ -418,7 +418,7 @@ func TestInitialPromptReadiness(t *testing.T) { SnapshotInterval: 1 * time.Second, ScreenStabilityLength: 2 * time.Second, AgentIO: &testAgent{screen: "loading..."}, - IsAgentReadyForInitialPrompt: func(message string) bool { + ReadyForInitialPrompt: func(message string) bool { return message == "ready" }, } @@ -431,7 +431,7 @@ func TestInitialPromptReadiness(t *testing.T) { // Even though screen is stable, status should be changing because agent is not ready assert.Equal(t, changing, c.Status()) - assert.False(t, c.AgentReadyForInitialPrompt) + assert.False(t, c.ReadyForInitialPrompt) assert.False(t, c.InitialPromptSent) }) @@ -441,7 +441,7 @@ func TestInitialPromptReadiness(t *testing.T) { SnapshotInterval: 1 * time.Second, ScreenStabilityLength: 2 * time.Second, AgentIO: &testAgent{screen: "loading..."}, - IsAgentReadyForInitialPrompt: func(message string) bool { + ReadyForInitialPrompt: func(message string) bool { return message == "ready" }, } @@ -458,7 +458,7 @@ func TestInitialPromptReadiness(t *testing.T) { c.AddSnapshot("ready") c.AddSnapshot("ready") assert.Equal(t, stable, c.Status()) - assert.True(t, c.AgentReadyForInitialPrompt) + assert.True(t, c.ReadyForInitialPrompt) assert.False(t, c.InitialPromptSent) }) @@ -468,7 +468,7 @@ func TestInitialPromptReadiness(t *testing.T) { SnapshotInterval: 1 * time.Second, ScreenStabilityLength: 2 * time.Second, AgentIO: &testAgent{screen: "loading..."}, - IsAgentReadyForInitialPrompt: func(message string) bool { + ReadyForInitialPrompt: func(message string) bool { return false // Agent never ready }, } @@ -481,7 +481,7 @@ func TestInitialPromptReadiness(t *testing.T) { // Status should be stable because no initial prompt to wait for assert.Equal(t, stable, c.Status()) - assert.False(t, c.AgentReadyForInitialPrompt) + assert.False(t, c.ReadyForInitialPrompt) assert.True(t, c.InitialPromptSent) // Set to true when initial prompt is empty }) @@ -491,7 +491,7 @@ func TestInitialPromptReadiness(t *testing.T) { SnapshotInterval: 1 * time.Second, ScreenStabilityLength: 2 * time.Second, AgentIO: &testAgent{screen: "processing..."}, - IsAgentReadyForInitialPrompt: func(message string) bool { + ReadyForInitialPrompt: func(message string) bool { return false // Agent never ready }, } @@ -505,7 +505,7 @@ func TestInitialPromptReadiness(t *testing.T) { // Status should be stable because initial prompt was already sent assert.Equal(t, stable, c.Status()) - assert.False(t, c.AgentReadyForInitialPrompt) + assert.False(t, c.ReadyForInitialPrompt) assert.True(t, c.InitialPromptSent) }) @@ -515,7 +515,7 @@ func TestInitialPromptReadiness(t *testing.T) { SnapshotInterval: 1 * time.Second, ScreenStabilityLength: 2 * time.Second, AgentIO: &testAgent{screen: "ready"}, - IsAgentReadyForInitialPrompt: func(message string) bool { + ReadyForInitialPrompt: func(message string) bool { return message == "ready" }, } @@ -526,20 +526,20 @@ func TestInitialPromptReadiness(t *testing.T) { c.AddSnapshot("ready") c.AddSnapshot("ready") assert.Equal(t, stable, c.Status()) - assert.True(t, c.AgentReadyForInitialPrompt) + assert.True(t, c.ReadyForInitialPrompt) // After agent is detected as ready, normal status logic applies // Screen changes should cause changing status c.AddSnapshot("changing") assert.Equal(t, changing, c.Status()) - assert.True(t, c.AgentReadyForInitialPrompt) + assert.True(t, c.ReadyForInitialPrompt) // Once screen stabilizes again, status should be stable - // AgentReadyForInitialPrompt remains true + // ReadyForInitialPrompt remains true c.AddSnapshot("stable now") c.AddSnapshot("stable now") c.AddSnapshot("stable now") assert.Equal(t, stable, c.Status()) - assert.True(t, c.AgentReadyForInitialPrompt) + assert.True(t, c.ReadyForInitialPrompt) }) } From d8057d5a6a9615b5d2beb6e577df6357da477324 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 21 Nov 2025 21:59:42 +0530 Subject: [PATCH 6/7] chore: update tests --- lib/screentracker/conversation_test.go | 64 ++++---------------------- 1 file changed, 9 insertions(+), 55 deletions(-) diff --git a/lib/screentracker/conversation_test.go b/lib/screentracker/conversation_test.go index 852a0bd..f77d114 100644 --- a/lib/screentracker/conversation_test.go +++ b/lib/screentracker/conversation_test.go @@ -409,14 +409,12 @@ func TestPartsToString(t *testing.T) { func TestInitialPromptReadiness(t *testing.T) { now := time.Now() - changing := st.ConversationStatusChanging - stable := st.ConversationStatusStable t.Run("agent not ready - status remains changing", func(t *testing.T) { cfg := st.ConversationConfig{ GetTime: func() time.Time { return now }, SnapshotInterval: 1 * time.Second, - ScreenStabilityLength: 2 * time.Second, + ScreenStabilityLength: 0, AgentIO: &testAgent{screen: "loading..."}, ReadyForInitialPrompt: func(message string) bool { return message == "ready" @@ -426,11 +424,9 @@ func TestInitialPromptReadiness(t *testing.T) { // Fill buffer with stable snapshots, but agent is not ready c.AddSnapshot("loading...") - c.AddSnapshot("loading...") - c.AddSnapshot("loading...") // Even though screen is stable, status should be changing because agent is not ready - assert.Equal(t, changing, c.Status()) + assert.Equal(t, st.ConversationStatusChanging, c.Status()) assert.False(t, c.ReadyForInitialPrompt) assert.False(t, c.InitialPromptSent) }) @@ -439,7 +435,7 @@ func TestInitialPromptReadiness(t *testing.T) { cfg := st.ConversationConfig{ GetTime: func() time.Time { return now }, SnapshotInterval: 1 * time.Second, - ScreenStabilityLength: 2 * time.Second, + ScreenStabilityLength: 0, AgentIO: &testAgent{screen: "loading..."}, ReadyForInitialPrompt: func(message string) bool { return message == "ready" @@ -449,15 +445,11 @@ func TestInitialPromptReadiness(t *testing.T) { // Agent not ready initially c.AddSnapshot("loading...") - c.AddSnapshot("loading...") - c.AddSnapshot("loading...") - assert.Equal(t, changing, c.Status()) + assert.Equal(t, st.ConversationStatusChanging, c.Status()) // Agent becomes ready c.AddSnapshot("ready") - c.AddSnapshot("ready") - c.AddSnapshot("ready") - assert.Equal(t, stable, c.Status()) + assert.Equal(t, st.ConversationStatusStable, c.Status()) assert.True(t, c.ReadyForInitialPrompt) assert.False(t, c.InitialPromptSent) }) @@ -466,7 +458,7 @@ func TestInitialPromptReadiness(t *testing.T) { cfg := st.ConversationConfig{ GetTime: func() time.Time { return now }, SnapshotInterval: 1 * time.Second, - ScreenStabilityLength: 2 * time.Second, + ScreenStabilityLength: 0, AgentIO: &testAgent{screen: "loading..."}, ReadyForInitialPrompt: func(message string) bool { return false // Agent never ready @@ -475,12 +467,10 @@ func TestInitialPromptReadiness(t *testing.T) { // Empty initial prompt means no need to wait for readiness c := st.NewConversation(context.Background(), cfg, "") - c.AddSnapshot("loading...") - c.AddSnapshot("loading...") c.AddSnapshot("loading...") // Status should be stable because no initial prompt to wait for - assert.Equal(t, stable, c.Status()) + assert.Equal(t, st.ConversationStatusStable, c.Status()) assert.False(t, c.ReadyForInitialPrompt) assert.True(t, c.InitialPromptSent) // Set to true when initial prompt is empty }) @@ -489,7 +479,7 @@ func TestInitialPromptReadiness(t *testing.T) { cfg := st.ConversationConfig{ GetTime: func() time.Time { return now }, SnapshotInterval: 1 * time.Second, - ScreenStabilityLength: 2 * time.Second, + ScreenStabilityLength: 0, AgentIO: &testAgent{screen: "processing..."}, ReadyForInitialPrompt: func(message string) bool { return false // Agent never ready @@ -499,47 +489,11 @@ func TestInitialPromptReadiness(t *testing.T) { // Manually mark as sent to simulate that initial prompt was already sent c.InitialPromptSent = true - c.AddSnapshot("processing...") - c.AddSnapshot("processing...") c.AddSnapshot("processing...") // Status should be stable because initial prompt was already sent - assert.Equal(t, stable, c.Status()) + assert.Equal(t, st.ConversationStatusStable, c.Status()) assert.False(t, c.ReadyForInitialPrompt) assert.True(t, c.InitialPromptSent) }) - - t.Run("agent readiness detected once - stays ready", func(t *testing.T) { - cfg := st.ConversationConfig{ - GetTime: func() time.Time { return now }, - SnapshotInterval: 1 * time.Second, - ScreenStabilityLength: 2 * time.Second, - AgentIO: &testAgent{screen: "ready"}, - ReadyForInitialPrompt: func(message string) bool { - return message == "ready" - }, - } - c := st.NewConversation(context.Background(), cfg, "initial prompt here") - - // Agent becomes ready - c.AddSnapshot("ready") - c.AddSnapshot("ready") - c.AddSnapshot("ready") - assert.Equal(t, stable, c.Status()) - assert.True(t, c.ReadyForInitialPrompt) - - // After agent is detected as ready, normal status logic applies - // Screen changes should cause changing status - c.AddSnapshot("changing") - assert.Equal(t, changing, c.Status()) - assert.True(t, c.ReadyForInitialPrompt) - - // Once screen stabilizes again, status should be stable - // ReadyForInitialPrompt remains true - c.AddSnapshot("stable now") - c.AddSnapshot("stable now") - c.AddSnapshot("stable now") - assert.Equal(t, stable, c.Status()) - assert.True(t, c.ReadyForInitialPrompt) - }) } From 96b16107887c2a024137d7212e3f59c356a6e1c4 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 21 Nov 2025 22:11:01 +0530 Subject: [PATCH 7/7] chore --- lib/httpapi/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/httpapi/server.go b/lib/httpapi/server.go index 948b2f8..18f2bf4 100644 --- a/lib/httpapi/server.go +++ b/lib/httpapi/server.go @@ -337,6 +337,7 @@ func (s *Server) StartSnapshotLoop(ctx context.Context) { s.logger.Error("Failed to send initial prompt", "error", err) } else { s.conversation.InitialPromptSent = true + s.conversation.ReadyForInitialPrompt = false currentStatus = st.ConversationStatusChanging s.logger.Info("Initial prompt sent successfully") }