@morlay/ui-conversation-message-actions 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -6
- package/dist/client.js +831 -0
- package/dist/index.d.mts +105 -0
- package/dist/index.mjs +3 -0
- package/dist/invariant.d.mts +7 -0
- package/dist/invariant.mjs +8 -0
- package/dist/plan-Bw4zoI4N.d.mts +86 -0
- package/dist/plan.d.mts +2 -0
- package/dist/plan.mjs +225 -0
- package/dist/src-CXYW0Bc0.mjs +408 -0
- package/dist/testing.d.mts +707 -0
- package/dist/testing.mjs +23690 -0
- package/package.json +60 -31
- package/src/client/chat-node/MessageEditDialog.module.css +24 -0
- package/src/client/chat-node/MessageEditDialog.tsx +71 -0
- package/src/client/chat-node/MessageIconActions.module.css +86 -0
- package/src/client/chat-node/MessageIconActions.tsx +189 -0
- package/src/client/chat-node/MessageItem.module.css +290 -0
- package/src/client/chat-node/MessageItem.tsx +198 -0
- package/src/client/chat-node/message-chrome.ts +61 -0
- package/src/client/chat-node/register.ts +33 -0
- package/src/client/chat-node/use-calendar-day.ts +23 -0
- package/src/client/controller.ts +327 -0
- package/src/client/css-modules.d.ts +4 -0
- package/src/client/import-action.tsx +74 -0
- package/src/client/index.ts +50 -0
- package/src/index.ts +650 -0
- package/src/invariant.ts +13 -0
- package/src/plan.ts +322 -0
- package/src/shared.ts +167 -0
- package/src/testing.ts +151 -0
- package/src/types.ts +73 -0
- package/lib/client.js +0 -2243
- package/lib/client.js.map +0 -1
- package/lib/index.d.mts +0 -211
- package/lib/index.d.mts.map +0 -1
- package/lib/index.mjs +0 -685
- package/lib/index.mjs.map +0 -1
- package/lib/invariant.d.mts +0 -15
- package/lib/invariant.d.mts.map +0 -1
- package/lib/invariant.mjs +0 -22
- package/lib/invariant.mjs.map +0 -1
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { _ as SessionEditorResult, d as EditableMessageBlock, f as RerollOperation, g as SessionEditorOperation, h as RewindOperation, l as retryableTurns, m as RetryableTurn, o as editableMessages, p as RetryOperation, r as closedTurns, u as EditOperation } from "./plan-Bw4zoI4N.mjs";
|
|
2
|
+
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
|
+
import { BranchBoundary, BranchTimeline, BranchTimeline as BranchTimeline$1, CascadePolicy, EditableBlockKind, SESSION_BRANCH_VERSION_SCHEMA, SessionBranchVersionEvent } from "@morlay/session-branch";
|
|
4
|
+
import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
5
|
+
import { UserMessage } from "@deepseek-ai/dsh-llm";
|
|
6
|
+
//#region src/index.d.ts
|
|
7
|
+
declare module "@deepseek-ai/cordis" {
|
|
8
|
+
interface Context {
|
|
9
|
+
sessionEditor: SessionEditor;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
interface EditorAgent {
|
|
13
|
+
readonly session: Session;
|
|
14
|
+
followup(message: UserMessage): void;
|
|
15
|
+
/** 等待 agent 到达 quiescence(当前 turn/任务结束后 resolve)。 */
|
|
16
|
+
whenIdle(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* 队列中是否有待处理输入(next-turn / next-step)。rewind 截断会删除这些
|
|
19
|
+
* 输入所属轮次的事件,残留的 inbox 状态会与截断后的 log 失配,使后续
|
|
20
|
+
* splice 落库后无法从日志重放——编辑前必须清空。
|
|
21
|
+
*/
|
|
22
|
+
readonly inboxPending: boolean;
|
|
23
|
+
/** 清空待处理输入(落库 canceled splice,须在 rewind 前调用)。 */
|
|
24
|
+
clearInbox(): void;
|
|
25
|
+
}
|
|
26
|
+
interface EditorAgentHandle {
|
|
27
|
+
readonly agent: EditorAgent;
|
|
28
|
+
dispose(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
interface EditorAgentRegistry {
|
|
31
|
+
get(sessionId: SessionId): EditorAgent | undefined;
|
|
32
|
+
create(options: {
|
|
33
|
+
sessionId?: SessionId;
|
|
34
|
+
seed?: readonly SessionEvent[];
|
|
35
|
+
meta?: {
|
|
36
|
+
cwd?: string;
|
|
37
|
+
parentSession?: SessionId;
|
|
38
|
+
seedLength?: number;
|
|
39
|
+
agentPreset?: string;
|
|
40
|
+
};
|
|
41
|
+
agentOptions?: {
|
|
42
|
+
provider: string;
|
|
43
|
+
model: string;
|
|
44
|
+
maxTokens?: number;
|
|
45
|
+
};
|
|
46
|
+
}): Promise<EditorAgentHandle>;
|
|
47
|
+
resume(options: {
|
|
48
|
+
resumeSessionId: SessionId;
|
|
49
|
+
agentOptions?: {
|
|
50
|
+
provider: string;
|
|
51
|
+
model: string;
|
|
52
|
+
maxTokens?: number;
|
|
53
|
+
};
|
|
54
|
+
}): Promise<EditorAgentHandle>;
|
|
55
|
+
}
|
|
56
|
+
declare class SessionEditor extends Service {
|
|
57
|
+
static inject: string[];
|
|
58
|
+
constructor(ctx: Context);
|
|
59
|
+
readBranchPrefix(id: SessionId, atSeq?: number, mode?: "after" | "before", signal?: AbortSignal): Promise<BranchBoundary>;
|
|
60
|
+
fork(sourceId: SessionId, atSeq?: number, childSessionId?: SessionId, meta?: {
|
|
61
|
+
cwd?: string;
|
|
62
|
+
agentPreset?: string;
|
|
63
|
+
}, signal?: AbortSignal): Promise<SessionId>;
|
|
64
|
+
rewind(id: SessionId, toBoundary: number, signal?: AbortSignal): Promise<import("@deepseek-ai/dsh-session-persistence").SessionPersistenceSnapshot>;
|
|
65
|
+
timeline(sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline$1>;
|
|
66
|
+
edit(operation: EditOperation, signal?: AbortSignal): Promise<SessionEditorResult>;
|
|
67
|
+
reroll(operation: RerollOperation, signal?: AbortSignal): Promise<SessionEditorResult>;
|
|
68
|
+
retry(operation: RetryOperation, signal?: AbortSignal): Promise<SessionEditorResult>;
|
|
69
|
+
editableMessages(sessionId: SessionId, signal?: AbortSignal): Promise<EditableMessageBlock[]>;
|
|
70
|
+
retryableTurns(sessionId: SessionId, signal?: AbortSignal): Promise<RetryableTurn[]>;
|
|
71
|
+
private branchOperation;
|
|
72
|
+
private readEvents;
|
|
73
|
+
/**
|
|
74
|
+
* rewind 前确保 agent 可驱动重放:live agent 先等待其停下(rewind 会截断其
|
|
75
|
+
* session 内存 log,须在 quiescence 后执行);cold 会话先 resume 出驻留
|
|
76
|
+
* agent(此时会话完整,resume 的 prepare 不与截断冲突)。agents 服务缺失或
|
|
77
|
+
* 无需重放时返回空——调用方退化为就地截断版本。
|
|
78
|
+
*/
|
|
79
|
+
private prepareReplay;
|
|
80
|
+
}
|
|
81
|
+
interface HttpRequestLike {
|
|
82
|
+
method?: string;
|
|
83
|
+
url?: string;
|
|
84
|
+
on(event: "data", listener: (chunk: Uint8Array | string) => void): this;
|
|
85
|
+
on(event: "end", listener: () => void): this;
|
|
86
|
+
on(event: "error", listener: (error: unknown) => void): this;
|
|
87
|
+
}
|
|
88
|
+
interface HttpResponseLike {
|
|
89
|
+
writeHead(status: number, headers?: Record<string, string>): unknown;
|
|
90
|
+
end(body?: string): void;
|
|
91
|
+
}
|
|
92
|
+
interface HttpServerLike {
|
|
93
|
+
register(route: {
|
|
94
|
+
kind: "exact";
|
|
95
|
+
path: string;
|
|
96
|
+
handler: (request: HttpRequestLike, response: HttpResponseLike) => void | Promise<void>;
|
|
97
|
+
}): () => void;
|
|
98
|
+
}
|
|
99
|
+
declare module "@deepseek-ai/cordis" {
|
|
100
|
+
interface Context {
|
|
101
|
+
webServer: HttpServerLike;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { type BranchTimeline, type CascadePolicy, type EditOperation, type EditableBlockKind, type EditableMessageBlock, EditorAgent, EditorAgentHandle, EditorAgentRegistry, type RerollOperation, type RetryOperation, type RetryableTurn, type RewindOperation, SESSION_BRANCH_VERSION_SCHEMA, type SessionBranchVersionEvent, SessionEditor, SessionEditor as default, type SessionEditorOperation, type SessionEditorResult, closedTurns, editableMessages, retryableTurns };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { n as SessionEditor, t as SESSION_BRANCH_VERSION_SCHEMA } from "./src-CXYW0Bc0.mjs";
|
|
2
|
+
import { closedTurns, editableMessages, retryableTurns } from "./plan.mjs";
|
|
3
|
+
export { SESSION_BRANCH_VERSION_SCHEMA, SessionEditor, SessionEditor as default, closedTurns, editableMessages, retryableTurns };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
//#region src/invariant.d.ts
|
|
3
|
+
declare const name = "ui-conversation-message-actions-invariant";
|
|
4
|
+
declare const inject: string[];
|
|
5
|
+
declare const apply: (ctx: Context) => Promise<() => void>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/invariant.ts
|
|
2
|
+
const PACKAGE_NAME = "@morlay/ui-conversation-message-actions";
|
|
3
|
+
const name = "ui-conversation-message-actions-invariant";
|
|
4
|
+
const inject = ["invariants"];
|
|
5
|
+
const install = () => {};
|
|
6
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
7
|
+
//#endregion
|
|
8
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { BranchTimeline, CascadePolicy, SessionBranchVersionEvent } from "@morlay/session-branch";
|
|
2
|
+
import { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
3
|
+
import { AssistantMessage, UserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
interface EditOperation {
|
|
6
|
+
action: "edit";
|
|
7
|
+
sessionId: SessionId;
|
|
8
|
+
eventSeq: number;
|
|
9
|
+
blockIndex: number;
|
|
10
|
+
text: string;
|
|
11
|
+
cascade: CascadePolicy;
|
|
12
|
+
}
|
|
13
|
+
interface RerollOperation {
|
|
14
|
+
action: "reroll";
|
|
15
|
+
sessionId: SessionId;
|
|
16
|
+
}
|
|
17
|
+
interface RetryOperation {
|
|
18
|
+
action: "retry";
|
|
19
|
+
sessionId: SessionId;
|
|
20
|
+
turn: number;
|
|
21
|
+
cascade: CascadePolicy;
|
|
22
|
+
}
|
|
23
|
+
interface RewindOperation {
|
|
24
|
+
action: "rewind";
|
|
25
|
+
sessionId: SessionId;
|
|
26
|
+
toBoundary: number;
|
|
27
|
+
}
|
|
28
|
+
interface ForkOperation {
|
|
29
|
+
action: "fork";
|
|
30
|
+
sessionId: SessionId;
|
|
31
|
+
atSeq?: number;
|
|
32
|
+
childSessionId?: SessionId;
|
|
33
|
+
}
|
|
34
|
+
type SessionEditorOperation = EditOperation | RerollOperation | RetryOperation | RewindOperation | ForkOperation;
|
|
35
|
+
interface SessionEditorResult {
|
|
36
|
+
sessionId: SessionId;
|
|
37
|
+
queuedTurns: number;
|
|
38
|
+
live?: boolean;
|
|
39
|
+
}
|
|
40
|
+
interface EditableMessageBlock {
|
|
41
|
+
key: string;
|
|
42
|
+
turn: number;
|
|
43
|
+
eventSeq: number;
|
|
44
|
+
blockIndex: number;
|
|
45
|
+
kind: "user" | "assistant.reasoning" | "assistant.response";
|
|
46
|
+
text: string;
|
|
47
|
+
time: number;
|
|
48
|
+
}
|
|
49
|
+
interface RetryableTurn {
|
|
50
|
+
turn: number;
|
|
51
|
+
userEventSeq: number;
|
|
52
|
+
preview: string;
|
|
53
|
+
time: number;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/plan.d.ts
|
|
57
|
+
interface ClosedTurn {
|
|
58
|
+
turn: number;
|
|
59
|
+
startSeq: number;
|
|
60
|
+
endSeq?: number;
|
|
61
|
+
closed: boolean;
|
|
62
|
+
user?: SessionEvent<"user/message">;
|
|
63
|
+
/** 轮内全部 user/message(agent 运行中 followup 追加的输入也计入)。 */
|
|
64
|
+
users: SessionEvent<"user/message">[];
|
|
65
|
+
assistants: SessionEvent<"assistant/message">[];
|
|
66
|
+
}
|
|
67
|
+
interface OperationPlan {
|
|
68
|
+
anchorSeq: number;
|
|
69
|
+
rewindBoundary?: number;
|
|
70
|
+
version: SessionBranchVersionEvent;
|
|
71
|
+
manualTurn?: {
|
|
72
|
+
turn: number;
|
|
73
|
+
user: UserMessage;
|
|
74
|
+
assistant: AssistantMessage;
|
|
75
|
+
};
|
|
76
|
+
queuedUsers: UserMessage[];
|
|
77
|
+
}
|
|
78
|
+
declare function closedTurns(events: readonly SessionEvent[]): ClosedTurn[];
|
|
79
|
+
declare function editableMessages(turns: readonly ClosedTurn[]): EditableMessageBlock[];
|
|
80
|
+
declare function retryableTurns(turns: readonly ClosedTurn[]): RetryableTurn[];
|
|
81
|
+
declare function downstreamUsers(turns: readonly ClosedTurn[], start: number): UserMessage[];
|
|
82
|
+
declare function editPlan(operation: EditOperation, turns: readonly ClosedTurn[]): OperationPlan;
|
|
83
|
+
declare function retryPlan(operation: RetryOperation, turns: readonly ClosedTurn[]): OperationPlan;
|
|
84
|
+
declare function rerollPlan(operation: RerollOperation, turns: readonly ClosedTurn[]): OperationPlan;
|
|
85
|
+
//#endregion
|
|
86
|
+
export { SessionEditorResult as _, editPlan as a, retryPlan as c, EditableMessageBlock as d, RerollOperation as f, SessionEditorOperation as g, RewindOperation as h, downstreamUsers as i, retryableTurns as l, RetryableTurn as m, OperationPlan as n, editableMessages as o, RetryOperation as p, closedTurns as r, rerollPlan as s, ClosedTurn as t, EditOperation as u };
|
package/dist/plan.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as editPlan, c as retryPlan, i as downstreamUsers, l as retryableTurns, n as OperationPlan, o as editableMessages, r as closedTurns, s as rerollPlan, t as ClosedTurn } from "./plan-Bw4zoI4N.mjs";
|
|
2
|
+
export { ClosedTurn, OperationPlan, closedTurns, downstreamUsers, editPlan, editableMessages, rerollPlan, retryPlan, retryableTurns };
|
package/dist/plan.mjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { SESSION_BRANCH_VERSION_SCHEMA, SessionBranchError } from "@morlay/session-branch";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
//#region src/plan.ts
|
|
4
|
+
function isTextualBlock(block) {
|
|
5
|
+
return block?.type === "text" || block?.type === "reasoning";
|
|
6
|
+
}
|
|
7
|
+
function userText(message) {
|
|
8
|
+
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
9
|
+
}
|
|
10
|
+
function cloneUser(message, content = structuredClone(message.content)) {
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
id: randomUUID(),
|
|
13
|
+
role: "user",
|
|
14
|
+
content: Object.freeze(content),
|
|
15
|
+
source: Object.freeze({ kind: "user" })
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function replaceTextBlock(content, blockIndex, text) {
|
|
19
|
+
const block = content[blockIndex];
|
|
20
|
+
if (!isTextualBlock(block)) throw new SessionBranchError("所选内容块不是可编辑文本。", "INVALID_BOUNDARY");
|
|
21
|
+
return content.map((candidate, index) => index === blockIndex ? {
|
|
22
|
+
...candidate,
|
|
23
|
+
text
|
|
24
|
+
} : structuredClone(candidate));
|
|
25
|
+
}
|
|
26
|
+
function closedTurns(events) {
|
|
27
|
+
const result = [];
|
|
28
|
+
let current;
|
|
29
|
+
for (const event of events) {
|
|
30
|
+
if (event.type === "turn/start") {
|
|
31
|
+
current = {
|
|
32
|
+
turn: event.data.turn,
|
|
33
|
+
startSeq: event.seq,
|
|
34
|
+
users: [],
|
|
35
|
+
assistants: []
|
|
36
|
+
};
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (current === void 0) continue;
|
|
40
|
+
if (event.type === "user/message" && event.data.source.kind === "user") {
|
|
41
|
+
if (current.user === void 0) current.user = event;
|
|
42
|
+
current.users.push(event);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (event.type === "assistant/message" && event.data.turn === current.turn) {
|
|
46
|
+
current.assistants.push(event);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (event.type === "turn/end" && event.data.turn === current.turn) {
|
|
50
|
+
result.push({
|
|
51
|
+
...current,
|
|
52
|
+
endSeq: event.seq,
|
|
53
|
+
closed: true
|
|
54
|
+
});
|
|
55
|
+
current = void 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (current !== void 0) result.push({
|
|
59
|
+
...current,
|
|
60
|
+
closed: false
|
|
61
|
+
});
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
function editableMessages(turns) {
|
|
65
|
+
const result = [];
|
|
66
|
+
for (const turn of turns) {
|
|
67
|
+
for (const user of turn.users) for (const [blockIndex, block] of user.data.content.entries()) {
|
|
68
|
+
if (block.type !== "text") continue;
|
|
69
|
+
result.push({
|
|
70
|
+
key: `${String(user.seq)}:${String(blockIndex)}`,
|
|
71
|
+
turn: turn.turn,
|
|
72
|
+
eventSeq: user.seq,
|
|
73
|
+
blockIndex,
|
|
74
|
+
kind: "user",
|
|
75
|
+
text: block.text,
|
|
76
|
+
time: user.time
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (!turn.closed) continue;
|
|
80
|
+
for (const event of turn.assistants) for (const [blockIndex, block] of event.data.message.content.entries()) {
|
|
81
|
+
if (!isTextualBlock(block)) continue;
|
|
82
|
+
result.push({
|
|
83
|
+
key: `${String(event.seq)}:${String(blockIndex)}`,
|
|
84
|
+
turn: turn.turn,
|
|
85
|
+
eventSeq: event.seq,
|
|
86
|
+
blockIndex,
|
|
87
|
+
kind: block.type === "reasoning" ? "assistant.reasoning" : "assistant.response",
|
|
88
|
+
text: block.text,
|
|
89
|
+
time: event.time
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
function retryableTurns(turns) {
|
|
96
|
+
return turns.flatMap((turn) => turn.user === void 0 || !turn.closed ? [] : [{
|
|
97
|
+
turn: turn.turn,
|
|
98
|
+
userEventSeq: turn.user.seq,
|
|
99
|
+
preview: userText(turn.user.data),
|
|
100
|
+
time: turn.user.time
|
|
101
|
+
}]);
|
|
102
|
+
}
|
|
103
|
+
function downstreamUsers(turns, start) {
|
|
104
|
+
return turns.slice(start).flatMap((turn) => turn.users.map((user) => cloneUser(user.data)));
|
|
105
|
+
}
|
|
106
|
+
function assistantReplacement(event, blockIndex, text) {
|
|
107
|
+
const replaced = replaceTextBlock(event.data.message.content, blockIndex, text).filter((block) => block.type === "text" || block.type === "reasoning");
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
id: randomUUID(),
|
|
110
|
+
role: "assistant",
|
|
111
|
+
content: Object.freeze(replaced),
|
|
112
|
+
source: Object.freeze({
|
|
113
|
+
kind: "model",
|
|
114
|
+
provider: event.data.message.source.provider,
|
|
115
|
+
model: event.data.message.source.model
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function pairVersionEffect(sourceSessionId, effect) {
|
|
120
|
+
return {
|
|
121
|
+
schemaVersion: SESSION_BRANCH_VERSION_SCHEMA,
|
|
122
|
+
effect: {
|
|
123
|
+
...effect,
|
|
124
|
+
id: randomUUID()
|
|
125
|
+
},
|
|
126
|
+
inverse: {
|
|
127
|
+
kind: "restore-version",
|
|
128
|
+
sessionId: sourceSessionId
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function editPlan(operation, turns) {
|
|
133
|
+
const turnIndex = turns.findIndex((turn) => operation.eventSeq > turn.startSeq && (turn.endSeq === void 0 || operation.eventSeq < turn.endSeq));
|
|
134
|
+
const turn = turns[turnIndex];
|
|
135
|
+
if (turn === void 0) throw new SessionBranchError("所选消息不属于已落定回合。", "INVALID_BOUNDARY");
|
|
136
|
+
const event = turn.users.find((candidate) => candidate.seq === operation.eventSeq) ?? turn.assistants.find((candidate) => candidate.seq === operation.eventSeq);
|
|
137
|
+
if (event === void 0) throw new SessionBranchError("所选消息不存在或不可编辑。", "INVALID_BOUNDARY");
|
|
138
|
+
if (event.type === "user/message") {
|
|
139
|
+
const before = event.data.content[operation.blockIndex];
|
|
140
|
+
if (before?.type !== "text") throw new SessionBranchError("所选用户消息块不是文本。", "INVALID_BOUNDARY");
|
|
141
|
+
const edited = cloneUser(event.data, replaceTextBlock(event.data.content, operation.blockIndex, operation.text));
|
|
142
|
+
const userIndex = turn.users.findIndex((candidate) => candidate.seq === event.seq);
|
|
143
|
+
const sameTurnFollowups = turn.users.slice(userIndex + 1).map((user) => cloneUser(user.data));
|
|
144
|
+
const later = operation.cascade === "preserve" ? downstreamUsers(turns, turnIndex + 1) : [];
|
|
145
|
+
return {
|
|
146
|
+
anchorSeq: turn.startSeq,
|
|
147
|
+
...turn.closed && userIndex === 0 ? {} : { rewindBoundary: event.seq },
|
|
148
|
+
version: pairVersionEffect(operation.sessionId, {
|
|
149
|
+
operation: "edit",
|
|
150
|
+
cascade: operation.cascade,
|
|
151
|
+
targetTurn: turn.turn,
|
|
152
|
+
targetEventSeq: event.seq,
|
|
153
|
+
targetBlockIndex: operation.blockIndex,
|
|
154
|
+
blockKind: "user",
|
|
155
|
+
before: before.text,
|
|
156
|
+
after: operation.text
|
|
157
|
+
}),
|
|
158
|
+
queuedUsers: [
|
|
159
|
+
edited,
|
|
160
|
+
...sameTurnFollowups,
|
|
161
|
+
...later
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (!turn.closed) throw new SessionBranchError("未闭合轮次的助手消息不可编辑。", "INVALID_BOUNDARY");
|
|
166
|
+
const before = event.data.message.content[operation.blockIndex];
|
|
167
|
+
if (!isTextualBlock(before)) throw new SessionBranchError("所选助手消息块不是文本或思考。", "INVALID_BOUNDARY");
|
|
168
|
+
const blockKind = before.type === "reasoning" ? "assistant.reasoning" : "assistant.response";
|
|
169
|
+
if (turn.user === void 0) throw new SessionBranchError("所选助手消息没有可重建的用户输入。", "INVALID_BOUNDARY");
|
|
170
|
+
return {
|
|
171
|
+
anchorSeq: turn.startSeq,
|
|
172
|
+
version: pairVersionEffect(operation.sessionId, {
|
|
173
|
+
operation: "edit",
|
|
174
|
+
cascade: operation.cascade,
|
|
175
|
+
targetTurn: turn.turn,
|
|
176
|
+
targetEventSeq: event.seq,
|
|
177
|
+
targetBlockIndex: operation.blockIndex,
|
|
178
|
+
blockKind,
|
|
179
|
+
before: before.text,
|
|
180
|
+
after: operation.text
|
|
181
|
+
}),
|
|
182
|
+
manualTurn: {
|
|
183
|
+
turn: turn.turn,
|
|
184
|
+
user: cloneUser(turn.user.data),
|
|
185
|
+
assistant: assistantReplacement(event, operation.blockIndex, operation.text)
|
|
186
|
+
},
|
|
187
|
+
queuedUsers: operation.cascade === "preserve" ? downstreamUsers(turns, turnIndex + 1) : []
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function retryPlan(operation, turns) {
|
|
191
|
+
const turnIndex = turns.findIndex((turn) => turn.turn === operation.turn);
|
|
192
|
+
const turn = turns[turnIndex];
|
|
193
|
+
if (turn?.user === void 0) throw new SessionBranchError("所选回合没有可重放的用户输入。", "INVALID_BOUNDARY");
|
|
194
|
+
return {
|
|
195
|
+
anchorSeq: turn.startSeq,
|
|
196
|
+
version: pairVersionEffect(operation.sessionId, {
|
|
197
|
+
operation: "retry",
|
|
198
|
+
cascade: operation.cascade,
|
|
199
|
+
targetTurn: turn.turn,
|
|
200
|
+
targetEventSeq: turn.user.seq
|
|
201
|
+
}),
|
|
202
|
+
queuedUsers: operation.cascade === "preserve" ? downstreamUsers(turns, turnIndex) : turn.users.map((user) => cloneUser(user.data))
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function rerollPlan(operation, turns) {
|
|
206
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
207
|
+
const turn = turns[index];
|
|
208
|
+
if (turn?.user === void 0 || !turn.closed) continue;
|
|
209
|
+
const target = turn.assistants.findLast((event) => event.data.message.content.some(isTextualBlock));
|
|
210
|
+
if (target === void 0) continue;
|
|
211
|
+
return {
|
|
212
|
+
anchorSeq: turn.startSeq,
|
|
213
|
+
version: pairVersionEffect(operation.sessionId, {
|
|
214
|
+
operation: "reroll",
|
|
215
|
+
cascade: "truncate",
|
|
216
|
+
targetTurn: turn.turn,
|
|
217
|
+
targetEventSeq: target.seq
|
|
218
|
+
}),
|
|
219
|
+
queuedUsers: turn.users.map((user) => cloneUser(user.data))
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
throw new SessionBranchError("当前会话没有可重生成的已落定助手回复。", "INVALID_BOUNDARY");
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
export { closedTurns, downstreamUsers, editPlan, editableMessages, rerollPlan, retryPlan, retryableTurns };
|