Skip to content

fix: preprompt would show after loading session#8744

Merged
matt2e merged 13 commits into
mainfrom
raw-messages-shown
Apr 22, 2026
Merged

fix: preprompt would show after loading session#8744
matt2e merged 13 commits into
mainfrom
raw-messages-shown

Conversation

@matt2e
Copy link
Copy Markdown
Collaborator

@matt2e matt2e commented Apr 22, 2026

Previously after loading a session:
Screenshot 2026-04-22 at 4 05 35 pm

When loading sessions we would show the preprompt text we added before starting the session.
We now separate the two into two text content blocks. The preprompt now has audience=[assistant], which can then filter out from the UI.

Summary

  • Replace XML wrapping of system prompts with ACP content block annotations (audience: ["assistant"]) to properly hide persona/project instructions from the user-visible message stream
  • Add audience annotation roundtripping: Rust server preserves annotations when converting between ACP and internal message formats, and propagates them back on replay
  • Filter annotated blocks in the UI so assistant-only content (system prompts) is not shown in MessageBubble
  • Deduplicate thinking/reasoning rendering branches in MessageBubble

Test plan

  • Existing tests pass (vitest, cargo clippy, biome check)
  • Verify persona instructions are sent as annotated blocks and hidden from the chat UI
  • Verify session replay correctly restores annotated user message blocks
  • Verify non-annotated user messages render normally

matt2e and others added 6 commits April 22, 2026 11:26
When the frontend sends a message with persona-instructions wrapping a
user-message, the backend now splits it into two persisted messages:
a hidden context message (user_visible: false) and a visible user message.
The agent still receives the full combined content. On session replay,
the hidden context message is filtered out, preventing raw XML tags
from being shown to the user.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of the frontend wrapping persona/project context and user message
in XML tags and the backend parsing them apart, the system prompt is now
sent as a separate field in the ACP _meta object. The backend reads it
directly from _meta.systemPrompt, creating hidden and visible messages
without any XML parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the custom _meta.systemPrompt extension with ACP's standard
annotations/audience mechanism. The frontend sends the system prompt as
a text content block with annotations: { audience: ["assistant"] }. The
backend preserves annotations through convert_acp_prompt_to_message and
session replay. The frontend filters out assistant-only blocks in
MessageBubble, preventing raw system prompt text from being shown to
the user.

This eliminates the _meta.systemPrompt field, backend message splitting
(hidden + visible), and the combined agent_message construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Short-circuit annotation filtering in MessageBubble for non-user
  messages to avoid allocating a new array on every assistant render.
- Extract filterUserVisibleContent helper and merge duplicate
  thinking/reasoning switch cases to stay within the file-size limit.
- Remove unused meta parameter from acpApi.prompt() (dead code since
  system prompt now uses annotations instead of _meta.systemPrompt).
- Add runtime typeof guard for annotations extraction in
  acpNotificationHandler instead of a bare 'as' cast, so malformed
  wire data won't silently produce an unexpected shape.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
- Restore sanitize_unicode_tags() for unannotated text blocks in
  convert_acp_prompt_to_message. The previous refactor bypassed
  sanitization for all text by constructing RawTextContent directly
  instead of calling with_text(). Now only annotated blocks (which
  originate from the frontend, not user input) skip sanitization.

- Skip with_audience() when the resolved audience vec is empty.
  Previously, annotations with no audience field produced
  Some(vec![]) via unwrap_or_default(), which downstream
  filter_for_audience treats as "visible to nobody" — silently
  dropping the block. Now an empty audience is treated as
  "no restriction" (no_annotation).

- Extract shared Audience type alias in messages.ts and use it in
  TextContent and acpNotificationHandler.ts via TextContent["annotations"],
  replacing inline ("user" | "assistant")[] literals so the type stays
  in sync across files.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Remove the type === "text" guard in filterUserVisibleContent so the
audience filter applies to any content block that carries annotations,
not just text blocks. This future-proofs the filter against other block
types (image, toolRequest, etc.) gaining audience annotations later.

Uses "annotations" in b to detect the field generically instead of
narrowing on the discriminated union variant.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92d86d004d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/goose2/src/shared/api/acpNotificationHandler.ts Outdated
matt2e and others added 3 commits April 22, 2026 13:52
- Remove unnecessary `as ContentBlock` cast in acp.ts since the SDK's
  ContentBlock union already includes TextContent with annotations.
- Extract shared ContentAnnotations interface in messages.ts so the
  annotation shape is reusable across content block types.
- Use ContentAnnotations in filterUserVisibleContent instead of an
  inline `{ audience?: string[] }` cast for type safety.
- Return null for user messages where all content blocks were filtered
  out (assistant-only system prompt with no visible user text), avoiding
  empty bubbles. Scoped to user role so streaming assistant messages
  with initially empty content still render.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
- Add optional annotations?: ContentAnnotations to all MessageContent
  variant interfaces (ImageContent, ToolRequestContent,
  ToolResponseContent, ThinkingContent, RedactedThinkingContent,
  ReasoningContent, ActionRequiredContent, SystemNotificationContent).
  TextContent already had it.
- Remove the 'as { annotations?: ContentAnnotations }' cast and
  'annotations in b' runtime check in filterUserVisibleContent — the
  compiler now verifies the .annotations access directly on the
  MessageContent union.
- Remove unused ContentAnnotations import from MessageBubble.tsx.
- Restore blank line between textContent derivation and the
  if (role === 'system') early-return for readability.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
User messages are never streamed token-by-token — each replay chunk is
already a complete content block. The previous coalescing logic merged
consecutive text chunks, which caused annotated (assistant-only system
prompt) and unannotated (user text) blocks to combine. The audience
filter then hid the entire merged block, making the user's message
disappear after session reload.

Always push a new content block for user_message_chunk instead of
appending to the previous one. Coalescing remains for agent_message_chunk
where token-by-token streaming requires reassembly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b444a3e588

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/goose/src/acp/server.rs
matt2e and others added 2 commits April 22, 2026 14:51
Annotated text blocks bypassed sanitize_unicode_tags because the
annotated branch constructed RawTextContent directly from text.text
instead of calling with_text(). This meant invisible Unicode Tag
characters in assistant-only system prompt blocks could be persisted
and sent to the model while being hidden from the user, undermining
the existing prompt-injection mitigation.

Apply sanitize_unicode_tags to all text blocks regardless of
annotation presence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Alphabetize grouped imports in use statements to satisfy cargo fmt
--check. Affects top-level imports and two function-scoped imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01bfa8881b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/goose2/src/shared/api/acpNotificationHandler.ts
matt2e and others added 2 commits April 22, 2026 15:18
Previously, assistant-only system prompt blocks (audience: ["assistant"])
were pushed into chat state during replay and only filtered out in
MessageBubble at render time. Other consumers reading raw message.content
(e.g. getTextContent in MessageTimeline) could still see the persona/
project instructions, affecting search and scroll behavior.

Filter out blocks whose audience does not include "user" at the replay
ingestion point, so they never enter chat state at all.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add length > 0 guard so that an empty audience array (audience: []) is
treated as "no restriction" rather than "visible to nobody". Applied to
both the replay ingestion filter in acpNotificationHandler and the
render-time filterUserVisibleContent in MessageBubble.

The backend already avoids emitting empty audience arrays, but this
hardens the frontend against unexpected wire data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c76c5e1de0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/goose/src/acp/server.rs
@matt2e matt2e changed the title fix: use content block annotations for audience-based system prompt filtering fix: preprompt sent to assistant only Apr 22, 2026
@matt2e matt2e changed the title fix: preprompt sent to assistant only fix: preprompt sent to with assistant as only audience Apr 22, 2026
@matt2e matt2e changed the title fix: preprompt sent to with assistant as only audience fix: preprompt would show after loading session Apr 22, 2026
@matt2e matt2e added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 4928ce5 Apr 22, 2026
25 checks passed
@matt2e matt2e deleted the raw-messages-shown branch April 22, 2026 23:20
lifeizhou-ap added a commit that referenced this pull request Apr 23, 2026
* main:
  fix: preprompt would show after loading session (#8744)
  commands to acp+ migration: extensions management (#8733)
  feat: desktop notification when goose finishes a task (#8647)
  harden code review skill for async state and default-resolution bugs (#8740)
  Feature/at agent mention (#8571)
  fix: removed hardcoded dependency of goose-acp-macro (#8753)
  perf: split agent setup into staged phases to reduce startup blocking (#8746)
  Add /skills command (#8600)
  Replace deprecated Claude ACP package links (#8625)
lifeizhou-ap added a commit that referenced this pull request Apr 23, 2026
* main: (34 commits)
  fix(goose-server): cache TLS cert to disk to avoid slow startup on first launch (#8174)
  feat: add Exa AI-powered search tool (#8487)
  fix: preprompt would show after loading session (#8744)
  commands to acp+ migration: extensions management (#8733)
  feat: desktop notification when goose finishes a task (#8647)
  harden code review skill for async state and default-resolution bugs (#8740)
  Feature/at agent mention (#8571)
  fix: removed hardcoded dependency of goose-acp-macro (#8753)
  perf: split agent setup into staged phases to reduce startup blocking (#8746)
  Add /skills command (#8600)
  Replace deprecated Claude ACP package links (#8625)
  removed the specific code owner for documentation change (#8749)
  fix(providers): handle missing delta field in streaming chunks (#8700)
  refactor(providers): extract http_status module and rename handle_status_openai_compat (#8620)
  fix(providers/openai): accept streaming chunks with both reasoning fields (#8715)
  feat: associate threads with projects (#8745)
  upgrade goose sdk and tui to be compatible with the latest agentclientprotocol/sdk package (#8667)
  feat: extend goose2 context window ux with auto-compaction (#8721)
  improve goose2 agent management flows (#8737)
  alexhancock/tui-improvements (#8736)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants