From d9cfd7c68ed6498f2aaf1c30dd7151713025228f Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:05:46 -0700 Subject: [PATCH] improvement(mothership): v0.9 (#6815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * checkpoint * Checkpoint * dot fixes * Make async tool resume delivery recoverable * Support split table tools and option recovery * Harden VFS mutation handling * feat(platform): platform subagent support — docs corpus VFS, search_docs, account context Squash of the feat/platform-agent branch (sim side): mounts the Sim docs corpus in the copilot VFS, wires search_docs and retires the legacy docs search tools, and syncs the generated tool catalog and trace contracts for the platform subagent. * Align Copilot tools and resource handling * Expand workflow log query support * checkpoint * Port desktop-improvements-0 desktop and browser-agent work * fix(desktop): keep browser-agent input alive through live-SPA re-renders * Harden workflow sanitization and Slack setup * feat(desktop): coordinate clicks, caret insertion, and drag for the browser agent * Revert subagent group eager auto-collapse * fix(chat): keep sends FIFO across the streaming-to-idle drain gap * Add the steering backend surface for mid-turn sends * Sync generated contracts for async subagent orchestration Pulls the mothership tool catalog (wait_agents / tail_agent / steer_agent / interrupt_agent), trace spans (chat.async_subagent.*, chat.orchestrate.*), and trace attributes (copilot.async_subagent.*) into the generated TS contracts. * Add display titles for the async subagent orchestration tools wait_agents / tail_agent / steer_agent / interrupt_agent get natural-language running titles (naming the agent id being waited on, tailed, steered, or stopped) and a Steering→Steered completed-verb rewrite. * Show orchestrator-chosen subagent names on agent groups A subagent_start whose payload data carries a name (the orchestrator's new name trigger parameter) now labels the agent group with that mission name — the agent-type icon stays. The name flows through the live stream path, the turn model (AgentNode.displayName) and its serialize/rebuild round-trip, and persisted transcripts (PersistedContentBlock.name), so reloads keep the label. * Improve Copilot error handling and logging * Backfill the subagent display name from the second start event The dispatch-time subagent_start fires before the trigger args (and therefore the name parameter) have streamed; the phase-3 start re-announces the lane with the name. The block builder was dropping that duplicate wholesale, losing the name on streaming providers — now it backfills subagentName onto the existing block instead. (The home turn-model path already reconciled this case.) * Support Slack bot connection flow * Harden Copilot error and VFS handling * Harden VFS resource operations * Show 'Waiting for the first of N agents' for mode-any waits The wait_agents title ignored the mode argument, so an any-mode wait over three agents read 'Waiting for 3 agents' while the model narrated waiting for the first — contradicting the transcript. * Collapsed-by-default agent cards with live intent status lines Subagents now narrate their work through 3-5 words tags (a fleet-wide prompt protocol on the mothership side). The turn model streams each subagent's text through a split-safe tag parser: complete tags update the agent's currentIntent and disappear from the prose, tags split across deltas are carried until their close arrives, and a tag that never closes flushes back as plain text. The agent card renders as one line — display name (or agent label) plus the latest intent, replaced inline as the agent shifts gears — and never auto-expands; expanding to the full tool log is a deliberate click. Only an outstanding permission prompt or a browser hand-back forces a group open. Intents persist on the subagent block (and through the legacy persisted- message paths) so reloads keep the last status, and a renamed reinvocation now takes the latest name instead of pinning the first. * Add the internal in-band tool execution route for live mothership turns POST /api/copilot/tools/execute (INTERNAL_API_SECRET, Go→Sim) runs one sim-server tool through the same server tool router the resume driver uses and returns the result synchronously — no checkpoint. This is what lets background (async) subagents write files/tables/knowledge, and lets the main lane keep streaming (instead of checkpoint-pausing and killing every background run) while async agents are live. * Persist resource side effects for in-band tool execution Files/tables created through the internal execute route now register on the chat's resources exactly like the resume driver's executions — the route runs the same handleResourceSideEffects pass (persistence only; an out-of-band route has no live event sink, so mid-turn chip pushes are a follow-up). * Extract intents from group text on every path, sync and async The turn-model intent filter only fires for span-scoped subagent lanes, but this surface also delivers subagent text through the legacy block path — so tags flowed through unparsed and rendered as prose rows. Groups now extract intents from their accumulated text at append time: the last complete tag becomes the card's status line and every complete tag is stripped from the rendered prose. Covers span-scoped, legacy, and persisted-reload paths for both synchronous and background delegations. * Fall back to the live tool title for the agent card status line Persisted data proved tool-first subagents (grok search agents) emit zero prose, so intent tags never stream no matter what the prompt says. The collapsed card now always narrates: the agent's own tag when present, else the latest tool's display title while the lane is live. * Catch subagent tags in the server relay The relay's subagent text handler now runs the split-safe intent extraction as chunks stream: the latest complete tag is stamped onto the lane's persisted subagent block (subagentIntent) and stripped from the stored prose, so live, persisted, and replayed views all agree. Per-lane carry handles tags split across chunks; a never-closing tag flushes back as plain text. * Drop the tool-title fallback: the status line is the agent's intent With the intent protocol now injected into every spawn's task message, agents open with an tag; the card shows that narration or nothing. * Replace intents with live tool-title status lines on agent cards Intent parsing is fully removed (turn model, relay handler, persistence fields, group extraction). The collapsed card's status is the latest tool call in its RUNNING phrasing — never the completed rewrite, which stays in the expanded log. Parallel tools show the most recently started still-running title with a +N for concurrent siblings; between rounds the last title stays frozen; a closed lane shows the bare name. Nested agent cards compute their own status recursively from their own items. * Keep the main Sim lane live-expanded; collapse only real subagent cards The mothership group is the turn's own narration, not a delegation card — collapsing it hid main-lane text and tools until manual expand, which read as mis-ordered streaming while async subagents interleaved. It keeps the original live-expand behavior and no status suffix. * Persist subagent lane lifecycle blocks from the span handler Lane-scoped span events route to the span handler, which only recorded trace side effects — no subagent start block was ever persisted (verified: a seven-agent run stored 104 blocks with zero starts). Grouping then fell back to keying lane content by agent NAME, so a respawned agent of the same type merged invisibly into the first one's card until it resolved. The handler now persists the start block (spanId-keyed and deduped, carrying the display name) and stamps endedAt on close, giving every invocation its own card. * Name agents in orchestration titles; '+ n more' overflow format wait/tail/steer/interrupt titles humanize the slugified agent ids back to their display names ('Waiting for the first of Digest Workflow Build + 4 more'), and the agent card's parallel-tool suffix uses the same '+ n more' format. * Harden in-band tool execution and resources * Route in-band execution through the comprehensive tool dispatcher The internal execute route used the bare server-tool router, which rejects VFS tools with 'Unknown server tool: read/glob/grep' — so nearly every background agent's first discovery call failed (102 in-band calls in one run, dozens rejected). It now uses the relay's executeTool dispatcher: registered handlers (VFS, function execute) with permission checks and param normalization, falling back to the app tool router — the same surface foreground execution gets. * Harden chat stream transition handling * Harden VFS provenance and resource writes * Standardize tool environment references * Harden browser panel and chat cleanup * Descriptive, user-language tool titles across the board House rules applied everywhere: use every argument the call carries, never name internal machinery, and never lead with Getting (the Got rewrite is deleted so it cannot return). - Deployments name the workflow: Deploying {workflow} as API/chat app/MCP tool - Workflow reads name the part: Reading {workflow} meta/state/deployment/notes; generic reads always name the file (Reading {leaf}), never bare Reading file - Block runs name block and workflow: Running {block} in {workflow}, Running from {block} in {workflow}, Running {workflow} until {block}, and Enabling/Disabling {block} in {workflow} - The six split-table tools get per-operation verbs (Adding column {name}, Updating rows, Wiring automation, Creating view {name}) instead of a wall of Querying table - The manage quartet drops X-action system-speak for gerunds - get_* internal names become user language (Checking run settings, Tracing block inputs, Reading the deployed version); web_fetch says Fetching - Scheduled-task titles removed entirely (feature deleted from the Go catalog) - New verb rewrites: Fetched, Traced, Wired, Configured, Looked, Rotated * Deploying {workflow} as chat, not as chat app * Loader gerunds; mv names both ends; mkdir names the folder search_integration_tools -> Finding the right integration; load_integration_tool -> Loading {integration} tools; load_skill -> Loading skill {name}; run_enrichment -> Looking up {subject}. mv prefers the model's phrasing, else reads 'Moving {files} to {destination}'; mkdir reads 'Creating folder {name}' from the path. * Overflow counts read '+ n', dropping 'more' * Unify workspace find and search * Scale desktop title bar with page zoom * Serialize account and organization truth into the copilot VFS Workspace standing, membership, billing, org role, access-control restrictions, published-block provenance, and fork topology were reachable only through three parameterless tools (or not at all). They are ambient read-only facts, so they belong in the VFS where they are greppable, cost no tool round-trip, and every agent that can read gets them — the same move that retired get_blocks_and_tools and list_user_workflows. Adds account/{workspace,workspaces,members,billing}.json (always mounted) and organization/{organization,access-control,custom-blocks,forks}.json (only when the workspace is org-hosted). Every file projects an existing use case or util after getOrMaterializeVFS's access assert — no new queries, no new authorization. One relation per file, cross-referenced by id-and-name stub, so overlapping facts cannot disagree. Volatile content (billing, access control, forks) is lazy, so numbers are read-time fresh and unasked-for reads cost nothing. Projection follows the viewer: member emails are admin-only, fork detail requires workspace admin on a forking-enabled org, and the whole organization/ namespace is absent for a personal workspace — which is itself the answer. Retires get_account_billing, get_enterprise_context, and list_user_workspaces along with their handlers; display titles stay for transcript replay. * Fix insert_text refusing an editable field focused inside a frame describeFocusedEditable descended shadow roots but not frames, while activeElementReadback descends both. Focus inside a same-origin frame therefore surfaced to the first as the FRAME element — not an input, not contentEditable, not a canvas, no textbox role — so it fell through to 'not-editable' and insert_text refused a field that press_key had just typed a character into. Two functions answering 'what is focused' with different answers is the bug; the descent loops now match exactly. The refusal also names what actually held focus (tag, role, contenteditable). A bare 'not-editable' gave the agent nothing to act on, so it guessed at the cause — a real run spent twenty rounds on the wrong theory and had to be stopped by the user. * Keep retired browser takeover renderable in history The tool is gone from the catalog, so its generated constant went with it and every path that referenced it stopped compiling. Deleting those paths instead would have silently downgraded every past transcript containing a takeover card to a generic tool row, and dropped the no-timeout budget that an in-flight takeover still needs while a rolling deploy finishes. retired-tools.ts gives the literal a documented home that says what it is and why it survives its tool. * Follow the agent into a tab it opened to work in browser_open_tab created the page with activate: false, so the agent worked in a tab the user could not see while the panel sat on a page where nothing was happening. The panel now follows a tab the agent deliberately opened. Scoped to that tool only. A page spawning its own tab (popup, target=_blank) is the site grabbing the view rather than the agent choosing a workspace, and stays in the background as before — two existing tests pin that and caught the first version of this change, which moved both. A tab the user claimed still wins over both: the work starts in the background instead of pulling the page out from under them mid-read. * Make the browser tools agree with each other An audit of the module found the frame-descent bug was one instance of a pattern: six independent definitions of 'is this editable' and seven of 'what is focused', disagreeing with each other. A tool refusing what its sibling accepts on identical page state is invisible at runtime — the agent follows a snapshot that says one thing into a tool that says another. - browser_type now accepts role="textbox" like browser_insert_text does. The snapshot advertises those elements as [textbox] with a ref, so refusing them meant rejecting exactly what the outline told the model to type into. Both the native and synthetic paths, and their descendant scans. - pressKeyOnPage descends shadow roots and frames like every other focus reader. It was dispatching synthetic keys at the shadow host or