Skip to content

fix(server): hide browser-context scaffolding in the new-tab agent chat#1095

Open
Nikhil (shadowfax92) wants to merge 1 commit into
mainfrom
fix/0601-1824-bobcat
Open

fix(server): hide browser-context scaffolding in the new-tab agent chat#1095
Nikhil (shadowfax92) wants to merge 1 commit into
mainfrom
fix/0601-1824-bobcat

Conversation

@shadowfax92
Copy link
Copy Markdown
Contributor

Summary

  • The new-tab Agents chat leaked the ## Browser Context … <USER_QUERY>…</USER_QUERY> prompt scaffolding in the user-message bubble, while the side panel showed only the clean query (see before/after below).
  • Root cause: the new-tab view renders the active turn's prompt (from GET /chat/active). For side-panel-initiated turns that stored prompt is the wrapped formatUserMessage output, and getActiveTurn returned it verbatim — the one read path that didn't strip.
  • Fix: getActiveTurn now unwraps the prompt on read via the canonical unwrapBrowserosAcpUserMessage, the same read-time strip getHistory and extractLastUserMessage already apply.

Design

Read-time normalization in AgentHarnessService.getActiveTurn, mirroring how getHistory de-scaffolds on read. The generic TurnRegistry keeps storing the faithful prompt (no layering violation), and the canonical unwrap helper is reused with zero duplicated logic. The change touches only the /chat/active read boundary — never the prompt sent to the LLM, and not the already-correct history/listing paths. A null prompt passes through unchanged, and the helper is idempotent so already-clean (new-tab-initiated) prompts are untouched.

Test plan

  • New unit tests in agent-harness-service.test.ts:
    • a wrapped ## Browser Context … <USER_QUERY> prompt surfaces via getActiveTurn as the clean inner query;
    • a null prompt passes through;
    • an already-clean prompt is unchanged (idempotence).
  • bun test across service + acpx-runtime + agents-route suites: 66/66 pass.
  • bunx biome check clean; tsc --noEmit exit 0.
  • Manual: start a turn from the side panel, then open the same agent in the new-tab Agents page — the user bubble now shows only the typed query, matching the side panel.

Out of scope

  • Prompts prefixed with a non-empty userSystemPrompt (pre-existing limitation shared with getHistory; the ^## Browser Context anchor won't match behind a prefix).
  • Whether new-tab-initiated turns receive browser context (separate product concern).

🤖 Generated with Claude Code

The new-tab Agents chat renders the active turn's prompt as the user
bubble. For sidepanel-initiated turns that prompt is the wrapped
formatUserMessage output, so the bubble leaked the '## Browser Context
… <USER_QUERY>…</USER_QUERY>' scaffolding while the side panel showed
the clean query.

getActiveTurn now unwraps the prompt on read via the canonical
unwrapBrowserosAcpUserMessage — the same read-time strip getHistory and
extractLastUserMessage already apply. Fixes the leak for every
/chat/active consumer without duplicating logic or touching the LLM
input.
@shadowfax92
Copy link
Copy Markdown
Contributor Author

Greptile (@greptileai) review

@shadowfax92
Copy link
Copy Markdown
Contributor Author

Claude (@claude) review

@github-actions github-actions Bot added the fix label Jun 2, 2026
@claude
Copy link
Copy Markdown

claude Bot commented Jun 2, 2026

Claude finished Nikhil (@shadowfax92)'s task in 1m 27s —— View job


Code Review

  • Gather context and understand request
  • Read changed files and diff
  • Review implementation in agent-harness-service.ts
  • Review tests in agent-harness-service.test.ts
  • Post review findings

Overall: LGTM — clean, minimal, well-targeted fix.

The approach is sound: read-time normalization at the getActiveTurn boundary mirrors how getHistory already de-scaffolds on read. The TurnRegistry stays unmodified (no layering violation), the LLM prompt path is untouched, and the canonical unwrapBrowserosAcpUserMessage helper is reused with no duplicated logic.

Implementation (agent-harness-service.ts:596–609)

getActiveTurn(agentId: string, sessionId: 'main' = 'main'): ActiveTurnInfo | null {
  const turn = this.turnRegistry.getActiveFor(agentId, sessionId)
  if (!turn) return null
  const info = this.turnRegistry.describe(turn.turnId)
  if (!info?.prompt) return info
  return { ...info, prompt: unwrapBrowserosAcpUserMessage(info.prompt) }
}

Correct. The if (!info?.prompt) return info guard handles:

  • null prompt — passes through unchanged ✓ (matches test at line 481)
  • info === null from describe() returning null — passes through ✓

One minor observation: unwrapBrowserosAcpUserMessage already has its own if (!raw) return raw guard, so the early return here is strictly redundant for the empty-string case. It's not a bug — the behavior is identical — but if describe() can return null, the guard also protects the object spread on the next line, which makes it load-bearing for the null case. So it's fine as-is.

Tests (agent-harness-service.test.ts:460–507)

Three new test cases covering exactly the right surface:

  1. Wrapped prompt strips to the inner query (line 460) — primary fix case ✓
  2. Null prompt passes through (line 481) — null guard ✓
  3. Clean prompt is unchanged (line 488) — idempotency ✓

The new serviceWithRegistry() helper (line 496) is a clean, reusable setup pattern. It directly registers into TurnRegistry to bypass startTurn, which avoids needing a full agent record for read-path tests.

Minor nit: serviceWithRegistry doesn't pass browserosDir, so the FileMessageQueue falls back to the real user dir (~/.browseros/...). This is a pre-existing pattern in this test file (the first test also omits it), and the queue isn't exercised in these tests, so it's fine. Not introduced by this PR.

Out-of-scope note (acknowledged in PR)

The userSystemPrompt prefix limitation (the ^## Browser Context regex anchor won't match behind a non-empty prefix) is a pre-existing limitation shared with getHistory. This PR correctly calls it out and doesn't attempt to address it.

No bugs, no security issues, no test gaps for the stated scope. Ready to merge.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

✅ Tests passed — 1105/1110

Suite Passed Failed Skipped
agent 99/99 0 0
build 14/14 0 0
eval 95/95 0 0
server-agent 246/246 0 0
server-api 86/86 0 0
server-browser 4/4 0 0
server-integration 9/10 0 1
server-lib 264/265 0 1
server-root 59/62 0 3
server-tools 229/229 0 0

View workflow run

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR fixes a UX bug where the new-tab Agents chat displayed raw browser-context scaffolding (## Browser Context … <USER_QUERY>…</USER_QUERY>) in the user message bubble instead of just the typed query.

  • Fix in getActiveTurn: applies the canonical unwrapBrowserosAcpUserMessage helper at read time, mirroring how getHistory and extractLastUserMessage already strip scaffolding; the stored prompt in TurnRegistry is not changed.
  • New unit tests in agent-harness-service.test.ts cover three cases: a fully-wrapped prompt (strips to clean query), a null prompt (passes through unchanged), and an already-clean prompt (idempotent).

Confidence Score: 5/5

Safe to merge — the change is a small, well-scoped read-time transformation at a single boundary, and the stored prompt is left untouched.

The fix is a one-liner that reuses the existing, well-tested unwrapBrowserosAcpUserMessage helper; the null/empty/clean guard is correct; and three targeted unit tests confirm all edge cases. No LLM prompt content, stored state, or existing read paths are affected.

No files require special attention.

Important Files Changed

Filename Overview
packages/browseros-agent/apps/server/src/api/services/agents/agent-harness-service.ts Adds read-time prompt unwrapping in getActiveTurn; logic is correct, handles null/empty/clean strings safely, and mirrors the existing pattern in getHistory.
packages/browseros-agent/apps/server/tests/api/services/agents/agent-harness-service.test.ts Adds three focused unit tests for getActiveTurn prompt unwrapping plus a clean serviceWithRegistry helper; all cases (wrapped, null, clean) are covered.

Sequence Diagram

sequenceDiagram
    participant UI as New-Tab Agent UI
    participant Route as GET /chat/active
    participant Service as AgentHarnessService
    participant Registry as TurnRegistry
    participant Helper as unwrapBrowserosAcpUserMessage

    UI->>Route: "GET /chat/active?agentId=…"
    Route->>Service: getActiveTurn(agentId)
    Service->>Registry: getActiveFor(agentId, 'main')
    Registry-->>Service: "ActiveTurn (status=running)"
    Service->>Registry: describe(turnId)
    Registry-->>Service: "ActiveTurnInfo { prompt: "## Browser Context…<USER_QUERY>question</USER_QUERY>" }"
    Service->>Helper: unwrapBrowserosAcpUserMessage(prompt)
    Helper-->>Service: "question"
    Service-->>Route: "{ …, prompt: "question" }"
    Route-->>UI: ActiveTurnInfo (clean prompt)
    UI->>UI: Render user bubble with "question"
Loading

Reviews (1): Last reviewed commit: "fix(server): strip browser-context scaff..." | Re-trigger Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR fixes a UI bug where the new-tab Agents chat leaked the raw ## Browser Context … <USER_QUERY>…</USER_QUERY> prompt scaffolding in the user-message bubble. The fix applies the existing unwrapBrowserosAcpUserMessage helper at the getActiveTurn read boundary in AgentHarnessService, mirroring the same stripping already done by getHistory and extractLastUserMessage.

  • agent-harness-service.ts: getActiveTurn now calls unwrapBrowserosAcpUserMessage on the stored prompt before returning it, with correct guards for null turns and null prompts.
  • agent-harness-service.test.ts: Three new unit tests verify the wrapped-prompt stripping, null passthrough, and idempotence on already-clean prompts; the serviceWithRegistry() factory is extracted for reuse.

Confidence Score: 5/5

Safe to merge — the change is a small, isolated read-time normalisation with no effect on the prompt sent to the LLM or on history/listing paths.

The fix reuses an already-tested, idempotent helper and adds correct null guards. Three new unit tests cover the wrapped, null, and clean-prompt cases. The affected scope is a single method on a single read boundary, and the stored data is never mutated.

No files require special attention.

Important Files Changed

Filename Overview
packages/browseros-agent/apps/server/src/api/services/agents/agent-harness-service.ts Adds read-time prompt unwrapping in getActiveTurn using the canonical unwrapBrowserosAcpUserMessage helper; guards correctly for null turn and null/empty prompt.
packages/browseros-agent/apps/server/tests/api/services/agents/agent-harness-service.test.ts Adds three focused unit tests for the new unwrapping behaviour (wrapped prompt, null prompt, idempotent clean prompt) plus a reusable serviceWithRegistry factory.

Sequence Diagram

sequenceDiagram
    participant UI as New-Tab Agent UI
    participant Route as /chat/active Route
    participant Service as AgentHarnessService
    participant Registry as TurnRegistry
    participant Helper as unwrapBrowserosAcpUserMessage

    UI->>Route: GET /chat/active (agentId)
    Route->>Service: getActiveTurn(agentId)
    Service->>Registry: getActiveFor(agentId, 'main')
    Registry-->>Service: ActiveTurn (or null)
    Service->>Registry: describe(turn.turnId)
    Registry-->>Service: "ActiveTurnInfo { prompt: "## Browser Context…<USER_QUERY>…" }"
    Service->>Helper: unwrapBrowserosAcpUserMessage(prompt)
    Helper-->>Service: "clean user query"
    Service-->>Route: "{ ...info, prompt: "clean user query" }"
    Route-->>UI: ActiveTurnInfo (clean prompt)
    UI->>UI: Renders clean user bubble
Loading

Reviews (2): Last reviewed commit: "fix(server): strip browser-context scaff..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant