feat: register browser tools and skills for queen

This commit is contained in:
Richard Tang
2026-04-08 15:29:00 -07:00
parent 5fe924318d
commit ea31b037b8
8 changed files with 60 additions and 31 deletions
@@ -5,5 +5,12 @@
"args": ["run", "python", "coder_tools_server.py", "--stdio"],
"cwd": "../../../../tools",
"description": "Unsandboxed file system tools for code generation and validation"
},
"gcu-tools": {
"transport": "stdio",
"command": "uv",
"args": ["run", "python", "-m", "gcu.server", "--stdio", "--capabilities", "browser"],
"cwd": "../../../../tools",
"description": "Browser automation tools (Playwright-based)"
}
}
+6 -13
View File
@@ -737,21 +737,14 @@ _queen_tools_independent = """
You are operating as a standalone agent no worker graph. You do the work directly.
## File I/O (coder-tools MCP)
- read_file(path, offset?, limit?, hashline?) read with line numbers
- write_file(path, content) create/overwrite, auto-mkdir
- edit_file(path, old_text, new_text, replace_all?) fuzzy-match edit
- hashline_edit(path, edits, auto_cleanup?, encoding?) anchor-based edit
- list_directory(path, recursive?) list contents
- search_files(pattern, path?, include?, hashline?) regex search
- run_command(command, cwd?, timeout?) shell execution
- undo_changes(path?) restore from git snapshot
- read_file, write_file, edit_file, hashline_edit, list_directory, \
search_files, run_command, undo_changes
## Browser Automation (gcu-tools MCP)
Full Playwright-based browser control: navigate, click, type, scroll, \
screenshot, evaluate JS, handle tabs, intercept requests, and more.
Use these tools to interact with websites, fill forms, extract data, \
and automate web workflows directly.
All browser tools are prefixed with `browser_` (browser_start, browser_navigate, \
browser_click, browser_fill, browser_snapshot, browser_screenshot, browser_scroll, \
browser_tabs, browser_close, browser_evaluate, etc.).
Follow the browser-automation skill protocol activate it before using browser tools.
"""
_queen_behavior_editing = """
+1 -1
View File
@@ -13,7 +13,7 @@ export interface LiveSession {
uptime_seconds: number;
intro_message?: string;
/** Queen operating phase — "planning", "building", "staging", or "running" */
queen_phase?: "planning" | "building" | "staging" | "running";
queen_phase?: "planning" | "building" | "staging" | "running" | "independent";
/** Whether the queen's LLM supports image content in messages */
queen_supports_images?: boolean;
/** Selected queen identity ID (e.g. "queen_technology") */
+12 -10
View File
@@ -47,7 +47,7 @@ export interface ChatMessage {
/** Epoch ms when this message was first created — used for ordering queen/worker interleaving */
createdAt?: number;
/** Queen phase active when this message was created */
phase?: "planning" | "building" | "staging" | "running";
phase?: "planning" | "building" | "staging" | "running" | "independent";
/** Images attached to a user message */
images?: ImageContent[];
/** Backend node_id that produced this message — used for subagent grouping */
@@ -86,7 +86,7 @@ interface ChatPanelProps {
/** Called when user dismisses the pending question without answering */
onQuestionDismiss?: () => void;
/** Queen operating phase — shown as a tag on queen messages */
queenPhase?: "planning" | "building" | "staging" | "running";
queenPhase?: "planning" | "building" | "staging" | "running" | "independent";
/** Context window usage for queen and workers */
contextUsage?: Record<string, ContextUsageEntry>;
}
@@ -210,7 +210,7 @@ const MessageBubble = memo(
queenPhase,
}: {
msg: ChatMessage;
queenPhase?: "planning" | "building" | "staging" | "running";
queenPhase?: "planning" | "building" | "staging" | "running" | "independent";
}) {
const isUser = msg.type === "user";
const isQueen = msg.role === "queen";
@@ -300,13 +300,15 @@ const MessageBubble = memo(
}`}
>
{isQueen
? (msg.phase ?? queenPhase) === "running"
? "running"
: (msg.phase ?? queenPhase) === "staging"
? "staging"
: (msg.phase ?? queenPhase) === "planning"
? "planning"
: "building"
? (msg.phase ?? queenPhase) === "independent"
? "independent"
: (msg.phase ?? queenPhase) === "running"
? "running"
: (msg.phase ?? queenPhase) === "staging"
? "staging"
: (msg.phase ?? queenPhase) === "planning"
? "planning"
: "building"
: "Worker"}
</span>
</div>
+2 -2
View File
@@ -149,8 +149,8 @@ export function sseEventToChatMessage(
}
}
type QueenPhase = "planning" | "building" | "staging" | "running";
const VALID_PHASES = new Set<string>(["planning", "building", "staging", "running"]);
type QueenPhase = "planning" | "building" | "staging" | "running" | "independent";
const VALID_PHASES = new Set<string>(["planning", "building", "staging", "running", "independent"]);
/**
* Scan an array of persisted events and return the last queen phase seen,
+6 -4
View File
@@ -47,7 +47,7 @@ function truncate(s: string, max: number): string {
type SessionRestoreResult = {
messages: ChatMessage[];
restoredPhase: "planning" | "building" | "staging" | "running" | null;
restoredPhase: "planning" | "building" | "staging" | "running" | "independent" | null;
flowchartMap: Record<string, string[]> | null;
originalDraft: DraftGraphData | null;
};
@@ -112,7 +112,7 @@ interface AgentState {
awaitingInput: boolean;
workerInputMessageId: string | null;
queenBuilding: boolean;
queenPhase: "planning" | "building" | "staging" | "running";
queenPhase: "planning" | "building" | "staging" | "running" | "independent";
designingDraft: boolean;
draftGraph: DraftGraphData | null;
originalDraft: DraftGraphData | null;
@@ -417,7 +417,7 @@ export default function ColonyChat() {
}
}
let restoredPhase: "planning" | "building" | "staging" | "running" | null = null;
let restoredPhase: "planning" | "building" | "staging" | "running" | "independent" | null = null;
let restoredFlowchartMap: Record<string, string[]> | null = null;
let restoredOriginalDraft: DraftGraphData | null = null;
@@ -958,7 +958,9 @@ export default function ColonyChat() {
const rawPhase = event.data?.phase as string;
const eventAgentPath = (event.data?.agent_path as string) || null;
const newPhase: AgentState["queenPhase"] =
rawPhase === "running"
rawPhase === "independent"
? "independent"
: rawPhase === "running"
? "running"
: rawPhase === "staging"
? "staging"
+10 -1
View File
@@ -35,6 +35,7 @@ export default function QueenDM() {
const turnCounterRef = useRef(0);
const queenIterTextRef = useRef<Record<string, Record<number, string>>>({});
const [queenPhase, setQueenPhase] = useState<"planning" | "building" | "staging" | "running" | "independent">("independent");
// Switch queen session when queenId changes
useEffect(() => {
@@ -195,6 +196,14 @@ export default function QueenDM() {
break;
}
case "queen_phase_changed": {
const rawPhase = event.data?.phase as string;
if (rawPhase === "independent" || rawPhase === "planning" || rawPhase === "building" || rawPhase === "staging" || rawPhase === "running") {
setQueenPhase(rawPhase);
}
break;
}
default:
break;
}
@@ -298,7 +307,7 @@ export default function QueenDM() {
isWaiting={isTyping && !isStreaming}
isBusy={isTyping}
disabled={loading || !queenReady}
queenPhase="planning"
queenPhase={queenPhase}
pendingQuestion={awaitingInput ? pendingQuestion : null}
pendingOptions={awaitingInput ? pendingOptions : null}
pendingQuestions={awaitingInput ? pendingQuestions : null}
+16
View File
@@ -0,0 +1,16 @@
from playwright.sync_api import sync_playwright
def main():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.linkedin.com/login")
print("Please log in to LinkedIn in the opened browser window.")
input("Press Enter here when you have logged in...")
# Now search connections
print("Logged in. Ready to proceed.")
browser.close()
if __name__ == "__main__":
main()