feat: add fork assistant message to new session

Add fork button on assistant messages to start new execution session
Include synthetic meta-instruction when forking to explain context
This commit is contained in:
Bohdan Triapitsyn
2025-12-09 00:40:43 +02:00
parent 96aa5bdd18
commit 2779e19103
13 changed files with 937 additions and 46 deletions
+22 -2
View File
@@ -3,6 +3,7 @@ import { create } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware";
import type { Message, Part } from "@opencode-ai/sdk";
import { opencodeClient } from "@/lib/opencode/client";
import { isExecutionForkMetaText } from "@/lib/messages/executionMeta";
import type { SessionMemoryState, MessageStreamLifecycle, AttachedFile } from "./types/sessionTypes";
import { MEMORY_LIMITS } from "./types/sessionTypes";
import {
@@ -332,7 +333,15 @@ export const useMessageStore = create<MessageStore>()(
userMessageMarker: message.info.role === "user" ? true : (message.info as any)?.userMessageMarker,
} as any;
const serverParts = Array.isArray(message.parts) ? [...message.parts] : [];
const serverParts = (Array.isArray(message.parts) ? message.parts : []).map((part) => {
if (part?.type === 'text') {
const raw = (part as any).text ?? (part as any).content ?? '';
if (isExecutionForkMetaText(raw)) {
return { ...part, synthetic: true } as Part;
}
}
return part;
});
const existingEntry = infoWithMarker?.id
? previousMessagesById.get(infoWithMarker.id as string)
: undefined;
@@ -935,6 +944,9 @@ export const useMessageStore = create<MessageStore>()(
}
const incomingText = extractTextFromPart(part);
if (isExecutionForkMetaText(incomingText)) {
(part as any).synthetic = true;
}
if (streamDebugEnabled() && actualRole === "assistant") {
try {
console.info("[STREAM-TRACE] part", {
@@ -1789,7 +1801,15 @@ export const useMessageStore = create<MessageStore>()(
: (message.info as any)?.animationSettled,
} as any;
const serverParts = Array.isArray(message.parts) ? [...message.parts] : [];
const serverParts = (Array.isArray(message.parts) ? message.parts : []).map((part) => {
if (part?.type === 'text') {
const raw = (part as any).text ?? (part as any).content ?? '';
if (isExecutionForkMetaText(raw)) {
return { ...part, synthetic: true } as Part;
}
}
return part;
});
const messageId = typeof infoWithMarker?.id === "string" ? (infoWithMarker.id as string) : undefined;
const existingEntry = messageId ? previousMessagesById.get(messageId) : undefined;