Skip to content

Add experimental sent-message editing - #1280

Merged
ymichael merged 9 commits into
mainfrom
bb/issue-1114-edit-messages
Aug 11, 2026
Merged

Add experimental sent-message editing#1280
ymichael merged 9 commits into
mainfrom
bb/issue-1114-edit-messages

Conversation

@ymichael

@ymichael ymichael commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a default-off editMessages experiment enforced by both the app and server
  • let users edit any eligible completed message without changing history until submission
  • reuse the queued-message inline editor styling; opening the editor focuses it without selecting its contents
  • support Codex, Claude Code, and Pi through provider-native transcript checkpoints
  • expose the atomic edit through the SDK and bb thread edit-message
  • bump the host daemon protocol

Behavior

Opening or cancelling the inline editor is client-local and non-destructive. Submitting an edit stages provider history through the turn before the selected message, atomically replaces the selected conversation suffix, keeps workspace changes, and starts the edited turn.

Claude Code uses the latest assistant UUID with forkSession and upToMessageId. Pi records the session leaf entry and branches through it. Legacy Claude Code or Pi history without a checkpoint rejects safely when a retained turn is required.

Review hardening

  • hash external operation IDs before deriving provider staging identities, preventing Pi path-sanitization collisions
  • preserve and validate agent caller identity through the contract, CLI, edit service, persisted replacement turn, and permission policy
  • recheck queued messages, pending interactions, and the editable event high-water mark in the atomic rewrite transaction
  • keep the timeline edit handler stable so every cached row regains its action after an active turn becomes idle
  • let connected realtime own history-rewrite refreshes, retaining mutation invalidation only as the disconnected fallback
  • replace the thread-only search-segment index with (thread_id, source_seq) and add a generated migration plus exact query-plan coverage

Validation

  • cross-package typechecks across the app, server, runtime, CLI, SDK, contracts, daemon, plugin SDK, templates, database, domain, and thread view
  • full affected-package tests: 2,491 app tests, 1,400 server tests, and 857 agent-runtime tests, plus CLI, daemon, contracts, SDK, database, domain, and thread-view suites
  • app lint and generated plugin SDK/template checks
  • latest review-fix pass: 106 focused app/server/runtime/CLI tests, all 375 database tests, and 227 server-contract/SDK/plugin-SDK/template tests
  • all 10 directly affected Turbo typecheck tasks pass; lint reports no errors
  • credentialed standalone-dev-server QA passed through the source-built CLI for Codex, Claude Code, and Pi: editing an earlier message replaced the suffix, reran the edited turn, and preserved the replacement context on the next turn
  • Pi QA confirmed the rewind session was adopted as the canonical provider session without leaving an operation-suffixed staging file

The Pi smoke pass used openai-codex/gpt-5.6-luna because the configured Anthropic route was out of extra usage.

Simplification pass

  • omitting expectedRequestSequence on POST /edit-message now atomically edits the latest eligible message; the read-only GET /latest-message-edit route, threads.getLatestMessageEdit, and the CLI's GET-then-POST resolution are removed (the CLI's edit-latest is no longer racy)
  • staged rewind forks are keyed by the server-minted per-attempt leaseId: each attempt owns exactly one fork, which removes the cross-attempt lease refcounting, join validation, and staging-id hashing, drops operationId from both daemon rewind commands, and replaces the runtime's provider allowlist with the supportsFork capability
  • the edit service parses stored events through parseStoredEvent with type narrowing, reuses hasRootStoredTurnStarted, builds the rewind-prepare command with a destructure-rest spread, and resolves the editable-turn scan in one query
  • the queued and sent-message inline editors share one composer builder and one blocked-reason model; the inline-queued attachment path shares useDraftAttachmentUploads; one-caller helpers and redundant prop round-trips are inlined
  • render-wiring and duplicate tests were pruned across timeline actions, settings, CLI, and the Pi bridge
  • net effect: the PR's hand-written diff (excluding generated Drizzle snapshot/bundled types and the plan doc) is ~5,150 insertions, down from ~5,900 before the pass, with all affected typechecks and full test suites passing (server 1,426, app 2,508, agent-runtime 904, daemon 523, plus CLI/SDK/contracts/templates)

Related to #1114

@ymichael
ymichael force-pushed the bb/issue-1114-edit-messages branch from d469223 to 29ce093 Compare August 10, 2026 20:43
@ymichael
ymichael marked this pull request as ready for review August 10, 2026 20:43
@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

I am the SlopCop. I am reviewing this pull request for security, code quality, performance, architecture, and end-to-end behavior.

Comment thread apps/server/src/services/threads/thread-edit-message.ts Outdated
Comment thread packages/server-contract/src/api/threads.ts
Comment thread packages/db/src/data/events.ts
Comment thread apps/app/src/views/thread-detail/sentMessageEdit.ts Outdated
Comment thread apps/app/src/views/thread-detail/ThreadDetailPromptArea.tsx Outdated
Comment thread apps/app/src/hooks/mutations/thread-runtime-mutations.ts
Comment thread apps/app/src/views/thread-detail/ThreadDetailView.tsx
Comment thread packages/server-contract/src/api/threads.ts Outdated
Comment thread packages/agent-runtime/src/runtime.ts Outdated

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🚨 SLOP COP 🚨 · review

ELI5: This feature lets a user change an old message. BB removes later chat events and asks the provider to answer again.

I found three defects that must be fixed before merge.

  • The edit waits for a host rewind without a shared thread lock. A send, queued message, or interaction can change state during that wait.
  • The edit API removes the agent caller identity. An agent can edit another root thread and start the replacement with user permission policy.
  • The API accepts any operation ID. Two different IDs can map to the same Pi session file and use the wrong checkpoint.

I also found six other defects.

  • Each prepared rewind keeps a provider session and two runtime entries until the daemon restarts.
  • The inline editor puts the caret at the end. It does not select the existing message.
  • A completed new turn restores the edit action only on the latest message. A page reload restores actions on earlier messages.
  • A connected client reloads history after realtime already reloads the same history.
  • The search suffix delete has no (threadId, sourceSeq) index. It can scan a full long-thread search history under the write lock.
  • The client does not check the active background-agent count. It can show an action that the server rejects.

The supported provider list also appears in the client, server, and runtime. A shared provider capability would reduce policy drift.

I tested the feature with the local app and a real Codex thread. A first-message edit worked, and an earlier edit removed the complete later suffix.

The final provider output was FINAL-REPLY. The removed secret and second turn were absent from the server timeline.

The five package type checks passed. The 68 focused app, server, and runtime tests passed.

@ymichael
ymichael force-pushed the bb/issue-1114-edit-messages branch 9 times, most recently from ad07b02 to 39654f5 Compare August 11, 2026 17:15
ymichael and others added 8 commits August 11, 2026 14:25
- fold latest-target resolution into POST /edit-message: omitting
  expectedRequestSequence atomically edits the latest eligible message,
  replacing the GET latest-message-edit route, SDK method, and the CLI's
  racy GET-then-POST two-step
- parse stored events through parseStoredEvent + type narrowing instead
  of hand-rolled JSON parsers, and reuse hasRootStoredTurnStarted
- build the rewind-prepare command with a destructure-rest spread,
  dropping the dead disallowedTools branch
- derive the request target kind from the staged fork instead of
  threading a redundant historyReplacement field
- reuse threadStartResultSchema for the identical prepare result shape
- migrate the inline-queued attachment path onto useDraftAttachmentUploads,
  inline the one-caller sentMessageEdit helpers, and drop the
  expectedRequestSequence round-trip and experiment-flag prop threading
  through the prompt area

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- key staged rewind forks by the server-minted per-attempt leaseId:
  each attempt owns one fork, dissolving the cross-attempt lease
  refcount, join validation, cleanup-wait loop, and sha256 staging-id
  hashing, and dropping operationId from both daemon wire commands
- replace the runtime's hardcoded rewind provider allowlist with the
  existing supportsFork capability, share one staged-discard helper,
  and merge the preparing/prepared maps into a single lease-keyed map
- collapse the editable-turn candidate scan to one query
- share one inline-draft-composer builder between the queued and
  sent-message editors and fold the sent editor's bespoke blocked
  reasons into the queued editor's ready/unavailable precedent
- prune render-wiring and duplicate tests across timeline actions,
  settings, CLI, and the Pi bridge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ymichael
ymichael force-pushed the bb/issue-1114-edit-messages branch from 9644f3c to fd28988 Compare August 11, 2026 21:28
@ymichael
ymichael merged commit 9699482 into main Aug 11, 2026
10 checks passed
@ymichael
ymichael deleted the bb/issue-1114-edit-messages branch August 11, 2026 21:35
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