Skip to content

feat: add message fork to new conversation - #609

Merged
chenyme merged 4 commits into
DEEIX-AI:devfrom
xmengnet:feat/message-fork
Aug 18, 2026
Merged

feat: add message fork to new conversation#609
chenyme merged 4 commits into
DEEIX-AI:devfrom
xmengnet:feat/message-fork

Conversation

@xmengnet

Copy link
Copy Markdown
Contributor

Implements the fork-to-a-new-conversation behavior discussed in #600 (deletion itself will follow in a separate change).

Summary

Issue #600 asks for per-message operations inside a conversation. Besides deleting a message, the discussion also asks for a way to branch off: copy the conversation from the start up to a chosen message into a new conversation, so the original stays untouched.

This PR adds the fork capability:

  • New endpoint POST /conversations/{id}/messages/{message_id}/fork copies the ancestor path (conversation start → chosen message, inclusive) into a new conversation via the existing recursive ancestor CTE. Cloned messages get fresh public IDs with parent links remapped; pending messages are normalized to interrupted so the fork never shows a stuck loading state.
  • Attachments are referenced instead of copied (same user, so no quota double-counting); files already deleted are skipped instead of failing the fork.
  • Billing, runs, process traces, semantic-recall chunks, and compaction snapshots are intentionally not carried over; the new conversation starts with a fresh sessionKey, empty lastResponseID, and no compaction state.
  • Frontend adds a fork button to both user and assistant message meta rows (animated GitFork icon following the animate-ui icon pattern), gated like retry/edit. After a successful fork the new conversation is renamed with a localized suffix ((分叉) / (Fork)), inserted into the sidebar recents, and the user is navigated to it.

Change type

  • Bug fix
  • Feature
  • Documentation
  • Refactor
  • Configuration / deployment
  • Security hardening
  • Other

Affected areas

  • Frontend / UI
  • Backend / API
  • Authentication / authorization
  • Conversations / streaming
  • Files / RAG / extraction
  • Model routing / providers
  • MCP / tools
  • Billing / payments
  • Admin console
  • Deployment / Docker / configuration
  • Documentation

Verification

  • Not run; reason:

  • go build ./..., go vet ./internal/application/conversation/... ./internal/transport/http/conversation/...

  • go test ./internal/application/conversation/... ./internal/transport/http/conversation/... (includes new table-driven test for normalizeForkedMessageStatus)

  • pnpm api:generate + pnpm api:check (no Swagger/TypeScript drift)

  • pnpm --filter @deeix/web typecheck

  • pnpm --filter @deeix/web lint (biome, full workspace)

  • pnpm --filter @deeix/web build

Screenshots, API examples, or logs

图片
POST /api/v1/conversations/{conversationPublicID}/messages/{messagePublicID}/fork
# no request body

200 response (standard errorMsg + data envelope, same shape as the clone-shared-conversation endpoint):

{
  "errorMsg": "",
  "data": {
    "publicID": "b3f1c2a4d5e6f7a8b9c0d1e2f3a4b5c6",
    "title": "Original title",
    "model": "gpt-4o",
    "provider": "openai",
    "messageCount": 6,
    "status": "active",
    "isStarred": false,
    "lastResponseID": "",
    "createdAt": "2026-08-16T12:00:00Z",
    "updatedAt": "2026-08-16T12:00:00Z"
  }
}

Errors: 400 when the target message is still generating (pending), 404 when the conversation or message does not exist or belongs to another user. An audit record fork_conversation (with source conversation/message IDs) is written on success.

Configuration, migration, and compatibility notes

  • No database schema, config, or environment changes; only existing tables and repository methods are used.
  • Purely additive API surface: one new endpoint. Swagger (backend/docs/) and packages/api-contract/src/types.generated.ts were regenerated together via pnpm api:generate.
  • Backward compatible: no existing route, DTO, or behavior was modified.

Documentation

  • Documentation is not needed for this change.
  • Documentation was updated.
  • Documentation still needs to be updated.

(The generated Swagger contract above is the API documentation for the new endpoint.)

Security and privacy

  • No secrets, tokens, credentials, local config, or personal data are included.
  • User data access remains scoped by authenticated user context unless an admin-only path explicitly requires broader access.
  • Security-sensitive behavior was reviewed, including authentication, authorization, provider routing, file processing, billing, admin APIs where relevant.

Ownership is enforced end to end: the conversation is loaded by publicID + userID, the message by publicID + userID, and the fork is rejected unless the message belongs to the requested conversation. Reused attachment files stay behind the existing cross-conversation reference counting, so the fork never exposes another user's files.

Checklist

  • I searched existing issues and pull requests.
  • Changes are focused and do not include unrelated refactors.
  • Tests or static verification were run where practical.
  • User-facing behavior, deployment steps, API contracts, or configuration changes are documented.
  • Generated artifacts are included only when this project explicitly requires them.
  • Caches, build output, .pyc files, .env files, and local storage data are not committed.

liyp and others added 4 commits August 16, 2026 18:27
Add POST /conversations/{id}/messages/{message_id}/fork which copies the
ancestor path from the conversation start up to the chosen message into a
new conversation. Cloned messages get fresh public IDs with parent links
remapped, attachments are referenced instead of copied, and billing, runs,
traces, and compaction snapshots are not carried over.

Frontend adds a fork button to user and assistant message meta rows with an
animated GitFork icon, renames the forked conversation with a localized
suffix, inserts it into the sidebar recents, and navigates to it.
@chenyme
chenyme self-requested a review August 18, 2026 02:14
@chenyme chenyme self-assigned this Aug 18, 2026
@chenyme
chenyme merged commit eb098f9 into DEEIX-AI:dev Aug 18, 2026
10 checks passed
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