@morlay/ui-conversation-message-actions 0.0.19-alpha.2 → 0.0.19-alpha.4
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 +4 -3
- package/dist/client.cjs +39 -179
- package/dist/index.d.cts +3 -6
- package/dist/index.d.mts +3 -6
- package/dist/index.mjs +8 -86
- package/dist/{plan-CT_6Y1fD.d.cts → plan-BKgGPZdD.d.mts} +2 -3
- package/dist/{plan-Cm9hCord.d.mts → plan-BUTplrpz.d.cts} +2 -3
- package/dist/plan.d.cts +1 -1
- package/dist/plan.d.mts +1 -1
- package/dist/plan.mjs +2 -49
- package/dist/shared-BBbOTwy4.mjs +4 -0
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.mts +2 -2
- package/dist/testing.mjs +582 -199
- package/package.json +24 -24
- package/src/client/chat-node/MessageItem.tsx +6 -3
- package/src/client/controller.ts +58 -221
- package/src/client/index.ts +0 -4
- package/src/index.ts +5 -143
- package/src/plan.ts +2 -54
- package/src/shared.ts +1 -96
- package/src/testing.ts +0 -2
- package/src/types.ts +1 -3
- package/dist/shared-LK3nImWW.mjs +0 -55
package/src/index.ts
CHANGED
|
@@ -13,35 +13,28 @@ import {
|
|
|
13
13
|
SessionBranchError,
|
|
14
14
|
balanceRewindPrefix,
|
|
15
15
|
type BranchBoundary,
|
|
16
|
-
type BranchTimeline,
|
|
17
16
|
} from "@morlay/session-branch";
|
|
18
17
|
import type {
|
|
19
18
|
EditOperation,
|
|
20
|
-
EditableMessageBlock,
|
|
21
19
|
RecallOperation,
|
|
22
20
|
RerollOperation,
|
|
23
21
|
RetryOperation,
|
|
24
|
-
RetryableTurn,
|
|
25
22
|
SessionEditorResult,
|
|
26
23
|
} from "./types.ts";
|
|
27
24
|
import {
|
|
28
25
|
SESSION_EDITOR_PATH,
|
|
29
26
|
type SessionEditorOperation,
|
|
30
27
|
type SessionEditorOperationResult,
|
|
31
|
-
type SessionEditorTimeline,
|
|
32
|
-
toTimelinePayload,
|
|
33
28
|
} from "./shared.ts";
|
|
34
29
|
import {
|
|
35
30
|
closedTurns,
|
|
36
31
|
droppedCompactions,
|
|
37
|
-
editableMessages,
|
|
38
32
|
editPlan,
|
|
39
33
|
precedingContentIndex,
|
|
40
34
|
recallBoundary,
|
|
41
35
|
restoreCompactions,
|
|
42
36
|
retryPlan,
|
|
43
37
|
rerollPlan,
|
|
44
|
-
retryableTurns,
|
|
45
38
|
} from "./plan.ts";
|
|
46
39
|
|
|
47
40
|
export type { CascadePolicy, EditableBlockKind } from "@morlay/session-branch";
|
|
@@ -57,10 +50,7 @@ export type {
|
|
|
57
50
|
SessionEditorResult,
|
|
58
51
|
} from "./types.ts";
|
|
59
52
|
|
|
60
|
-
export { closedTurns, editableMessages, retryableTurns };
|
|
61
|
-
export { SESSION_BRANCH_VERSION_SCHEMA } from "@morlay/session-branch";
|
|
62
|
-
|
|
63
|
-
export type { BranchTimeline, SessionBranchVersionEvent } from "@morlay/session-branch";
|
|
53
|
+
export { closedTurns, editableMessages, retryableTurns } from "./plan.ts";
|
|
64
54
|
|
|
65
55
|
declare module "@deepseek-ai/cordis" {
|
|
66
56
|
interface Context {
|
|
@@ -102,18 +92,12 @@ export interface EditorAgentRegistry {
|
|
|
102
92
|
}): Promise<EditorAgentHandle>;
|
|
103
93
|
}
|
|
104
94
|
|
|
105
|
-
function appendLogSeedEvent(
|
|
106
|
-
events: SessionEvent[],
|
|
107
|
-
type: string,
|
|
108
|
-
data: unknown,
|
|
109
|
-
ignorable = false,
|
|
110
|
-
): void {
|
|
95
|
+
function appendLogSeedEvent(events: SessionEvent[], type: string, data: unknown): void {
|
|
111
96
|
events.push({
|
|
112
97
|
type: type as SessionEvent["type"],
|
|
113
98
|
seq: events.length,
|
|
114
99
|
time: Date.now(),
|
|
115
100
|
data: data as SessionEvent["data"],
|
|
116
|
-
...(ignorable ? { ignorable: true as const } : {}),
|
|
117
101
|
} as SessionEvent);
|
|
118
102
|
}
|
|
119
103
|
|
|
@@ -165,22 +149,8 @@ function keepFromOf(events: readonly SessionEvent[], boundary: number): number {
|
|
|
165
149
|
async function appendSeedSuffixLive(
|
|
166
150
|
session: Session,
|
|
167
151
|
seedSuffix: readonly SessionEvent[],
|
|
168
|
-
appendDirect: (events: readonly SessionEvent[]) => Promise<void>,
|
|
169
152
|
): Promise<void> {
|
|
170
153
|
for (const event of seedSuffix) {
|
|
171
|
-
const ignorable = (event as { ignorable?: boolean }).ignorable === true;
|
|
172
|
-
if (ignorable) {
|
|
173
|
-
const s = session as unknown as {
|
|
174
|
-
log: SessionEvent[];
|
|
175
|
-
eventsSnapshot?: unknown;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const seq = s.log.length;
|
|
179
|
-
await appendDirect([{ ...event, seq } as SessionEvent]);
|
|
180
|
-
s.log.push({ ...event, seq } as SessionEvent);
|
|
181
|
-
s.eventsSnapshot = undefined;
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
154
|
const s = session as unknown as {
|
|
185
155
|
append(
|
|
186
156
|
type: string,
|
|
@@ -244,10 +214,6 @@ export class SessionEditor extends Service {
|
|
|
244
214
|
return this.ctx.sessionBranch.rewind(id, toBoundary, signal);
|
|
245
215
|
}
|
|
246
216
|
|
|
247
|
-
timeline(sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline> {
|
|
248
|
-
return this.ctx.sessionBranch.timeline(sessionId, signal);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
217
|
edit(operation: EditOperation, signal?: AbortSignal): Promise<SessionEditorResult> {
|
|
252
218
|
return this.branchOperation(operation, signal);
|
|
253
219
|
}
|
|
@@ -264,19 +230,6 @@ export class SessionEditor extends Service {
|
|
|
264
230
|
return this.recallOperation(operation, signal);
|
|
265
231
|
}
|
|
266
232
|
|
|
267
|
-
async editableMessages(
|
|
268
|
-
sessionId: SessionId,
|
|
269
|
-
signal?: AbortSignal,
|
|
270
|
-
): Promise<EditableMessageBlock[]> {
|
|
271
|
-
const events = await this.readEvents(sessionId, signal);
|
|
272
|
-
return editableMessages(closedTurns(events));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async retryableTurns(sessionId: SessionId, signal?: AbortSignal): Promise<RetryableTurn[]> {
|
|
276
|
-
const events = await this.readEvents(sessionId, signal);
|
|
277
|
-
return retryableTurns(closedTurns(events));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
233
|
private async branchOperation(
|
|
281
234
|
operation: EditOperation | RerollOperation | RetryOperation,
|
|
282
235
|
signal?: AbortSignal,
|
|
@@ -309,8 +262,6 @@ export class SessionEditor extends Service {
|
|
|
309
262
|
|
|
310
263
|
const replayTurn = preceding < 0 ? 1 : turns[preceding]!.turn + 1;
|
|
311
264
|
|
|
312
|
-
const versionSeed: SessionEvent[] = [];
|
|
313
|
-
appendLogSeedEvent(versionSeed, "session-branch/version", plan.version, true);
|
|
314
265
|
const manualSeed: SessionEvent[] = [];
|
|
315
266
|
if (plan.manualTurn !== undefined) {
|
|
316
267
|
appendManualTurn(manualSeed, { ...plan.manualTurn, turn: replayTurn });
|
|
@@ -326,11 +277,10 @@ export class SessionEditor extends Service {
|
|
|
326
277
|
await this.stopLoop(operation.sessionId, signal);
|
|
327
278
|
const live = this.ctx.sessions.get(operation.sessionId);
|
|
328
279
|
await this.ctx.sessionBranch.rewind(operation.sessionId, boundary, signal);
|
|
329
|
-
//
|
|
280
|
+
// 补回的压缩落在重放的输入之前
|
|
330
281
|
const seedStart = this.seedStart(live, events, keepFrom);
|
|
331
282
|
const seedSuffix: SessionEvent[] = [
|
|
332
|
-
...
|
|
333
|
-
...restoreCompactions(compactions, keepFrom, seedStart + versionSeed.length),
|
|
283
|
+
...restoreCompactions(compactions, keepFrom, seedStart),
|
|
334
284
|
...manualSeed,
|
|
335
285
|
];
|
|
336
286
|
await this.appendSeedSuffix(operation.sessionId, live, seedSuffix, events, keepFrom);
|
|
@@ -402,22 +352,8 @@ export class SessionEditor extends Service {
|
|
|
402
352
|
): Promise<void> {
|
|
403
353
|
if (seedSuffix.length === 0) return;
|
|
404
354
|
if (live !== undefined) {
|
|
405
|
-
const persistence = this.ctx.sessionPersistence as unknown as {
|
|
406
|
-
tracker: {
|
|
407
|
-
writerOf(id: SessionId):
|
|
408
|
-
| {
|
|
409
|
-
append(events: readonly SessionEvent[]): Promise<void>;
|
|
410
|
-
}
|
|
411
|
-
| undefined;
|
|
412
|
-
};
|
|
413
|
-
};
|
|
414
|
-
const handle = persistence.tracker.writerOf(sessionId);
|
|
415
|
-
if (handle === undefined) {
|
|
416
|
-
throw new Error(`session "${sessionId}" has no live write handle for the version effect`);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
355
|
await this.ctx.sessions.flush(live);
|
|
420
|
-
await appendSeedSuffixLive(live, seedSuffix
|
|
356
|
+
await appendSeedSuffixLive(live, seedSuffix);
|
|
421
357
|
await this.ctx.sessions.flush(live);
|
|
422
358
|
return;
|
|
423
359
|
}
|
|
@@ -631,16 +567,6 @@ function respondJson(response: HttpResponseLike, status: number, value: unknown)
|
|
|
631
567
|
response.end(JSON.stringify(value));
|
|
632
568
|
}
|
|
633
569
|
|
|
634
|
-
async function readTimeline(
|
|
635
|
-
editor: SessionEditor,
|
|
636
|
-
sessionId: SessionId,
|
|
637
|
-
): Promise<SessionEditorTimeline> {
|
|
638
|
-
const timeline = await editor.timeline(sessionId);
|
|
639
|
-
const messages = await editor.editableMessages(sessionId);
|
|
640
|
-
const retryable = await editor.retryableTurns(sessionId);
|
|
641
|
-
return toTimelinePayload(sessionId, timeline, messages, retryable);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
570
|
async function runOperation(
|
|
645
571
|
editor: SessionEditor,
|
|
646
572
|
operation: SessionEditorOperation,
|
|
@@ -699,12 +625,6 @@ async function handleRoute(
|
|
|
699
625
|
response: HttpResponseLike,
|
|
700
626
|
): Promise<void> {
|
|
701
627
|
try {
|
|
702
|
-
if (request.method === "GET") {
|
|
703
|
-
const url = new URL(request.url ?? SESSION_EDITOR_PATH, "http://session-editor.local");
|
|
704
|
-
const sessionId = sessionIdOf(url.searchParams.get("sessionId"));
|
|
705
|
-
respondJson(response, 200, await readTimeline(editor, sessionId));
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
628
|
if (request.method === "POST") {
|
|
709
629
|
respondJson(
|
|
710
630
|
response,
|
|
@@ -736,62 +656,4 @@ function registerHttpRoutes(ctx: Context): void {
|
|
|
736
656
|
handler: (request, response) => handleRoute(editor, request, response),
|
|
737
657
|
});
|
|
738
658
|
}, "session-editor: HTTP route");
|
|
739
|
-
|
|
740
|
-
const registerConnectionRoute = (): void => {
|
|
741
|
-
const connection = ctx.get("connection", false) as
|
|
742
|
-
| {
|
|
743
|
-
fetch: {
|
|
744
|
-
register(route: {
|
|
745
|
-
path: string;
|
|
746
|
-
methods: readonly ("GET" | "HEAD" | "POST")[];
|
|
747
|
-
requestBody: "buffered" | "streaming";
|
|
748
|
-
fetch: (request: Request) => Promise<Response>;
|
|
749
|
-
}): () => Promise<void>;
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
| undefined;
|
|
753
|
-
if (connection === undefined) return;
|
|
754
|
-
const editor = ctx.sessionEditor;
|
|
755
|
-
ctx.effect(
|
|
756
|
-
() =>
|
|
757
|
-
connection.fetch.register({
|
|
758
|
-
path: `/api${SESSION_EDITOR_PATH}`,
|
|
759
|
-
methods: ["GET", "POST"],
|
|
760
|
-
requestBody: "buffered",
|
|
761
|
-
fetch: (request) => handleFetchRoute(editor, request),
|
|
762
|
-
}),
|
|
763
|
-
"session-editor: connection fetch route",
|
|
764
|
-
);
|
|
765
|
-
};
|
|
766
|
-
ctx.on("internal/service", (name) => {
|
|
767
|
-
if (name === "connection") registerConnectionRoute();
|
|
768
|
-
});
|
|
769
|
-
registerConnectionRoute();
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
async function handleFetchRoute(editor: SessionEditor, request: Request): Promise<Response> {
|
|
773
|
-
try {
|
|
774
|
-
if (request.method === "GET") {
|
|
775
|
-
const url = new URL(request.url);
|
|
776
|
-
const sessionId = sessionIdOf(url.searchParams.get("sessionId"));
|
|
777
|
-
return jsonResponse(200, await readTimeline(editor, sessionId));
|
|
778
|
-
}
|
|
779
|
-
if (request.method === "POST") {
|
|
780
|
-
return jsonResponse(200, await runOperation(editor, decodeOperation(await request.json())));
|
|
781
|
-
}
|
|
782
|
-
return new Response(null, { status: 405 });
|
|
783
|
-
} catch (error: unknown) {
|
|
784
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
785
|
-
return jsonResponse(error instanceof TypeError ? 400 : 409, { error: message });
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
function jsonResponse(status: number, value: unknown): Response {
|
|
790
|
-
return new Response(JSON.stringify(value), {
|
|
791
|
-
status,
|
|
792
|
-
headers: {
|
|
793
|
-
"content-type": "application/json; charset=utf-8",
|
|
794
|
-
"cache-control": "no-store",
|
|
795
|
-
},
|
|
796
|
-
});
|
|
797
659
|
}
|
package/src/plan.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { AssistantMessage, ContentBlock, UserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { SessionSeq } from "@deepseek-ai/dsh-session";
|
|
4
|
-
import type { SessionEvent
|
|
5
|
-
import {
|
|
6
|
-
SESSION_BRANCH_VERSION_SCHEMA,
|
|
7
|
-
SessionBranchError,
|
|
8
|
-
type EditableBlockKind,
|
|
9
|
-
type SessionBranchEffect,
|
|
10
|
-
type SessionBranchVersionEvent,
|
|
11
|
-
} from "@morlay/session-branch";
|
|
4
|
+
import type { SessionEvent } from "@deepseek-ai/dsh-session";
|
|
5
|
+
import { SessionBranchError } from "@morlay/session-branch";
|
|
12
6
|
import type {
|
|
13
7
|
EditOperation,
|
|
14
8
|
EditableMessageBlock,
|
|
@@ -34,7 +28,6 @@ export interface OperationPlan {
|
|
|
34
28
|
anchorSeq: number;
|
|
35
29
|
|
|
36
30
|
rewindBoundary?: number;
|
|
37
|
-
version: SessionBranchVersionEvent;
|
|
38
31
|
|
|
39
32
|
manualTurn?: { turn: number; user: UserMessage; assistant: AssistantMessage };
|
|
40
33
|
|
|
@@ -220,17 +213,6 @@ function assistantReplacement(
|
|
|
220
213
|
}) as AssistantMessage;
|
|
221
214
|
}
|
|
222
215
|
|
|
223
|
-
function pairVersionEffect(
|
|
224
|
-
sourceSessionId: SessionId,
|
|
225
|
-
effect: Omit<SessionBranchEffect, "id">,
|
|
226
|
-
): SessionBranchVersionEvent {
|
|
227
|
-
return {
|
|
228
|
-
schemaVersion: SESSION_BRANCH_VERSION_SCHEMA,
|
|
229
|
-
effect: { ...effect, id: randomUUID() },
|
|
230
|
-
inverse: { kind: "restore-version", sessionId: sourceSessionId },
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
216
|
export function editPlan(operation: EditOperation, turns: readonly ClosedTurn[]): OperationPlan {
|
|
235
217
|
const turnIndex = turns.findIndex(
|
|
236
218
|
(turn) =>
|
|
@@ -262,16 +244,6 @@ export function editPlan(operation: EditOperation, turns: readonly ClosedTurn[])
|
|
|
262
244
|
anchorSeq: turn.startSeq,
|
|
263
245
|
|
|
264
246
|
...(userIndex === 0 ? {} : { rewindBoundary: event.seq }),
|
|
265
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
266
|
-
operation: "edit",
|
|
267
|
-
cascade: operation.cascade,
|
|
268
|
-
targetTurn: turn.turn,
|
|
269
|
-
targetEventSeq: event.seq,
|
|
270
|
-
targetBlockIndex: operation.blockIndex,
|
|
271
|
-
blockKind: "user",
|
|
272
|
-
before: before.text,
|
|
273
|
-
after: operation.text,
|
|
274
|
-
}),
|
|
275
247
|
queuedUsers: [edited, ...sameTurnFollowups, ...later],
|
|
276
248
|
targetSeq: event.seq,
|
|
277
249
|
};
|
|
@@ -282,22 +254,10 @@ export function editPlan(operation: EditOperation, turns: readonly ClosedTurn[])
|
|
|
282
254
|
const before = event.data.message.content[operation.blockIndex];
|
|
283
255
|
if (!isTextualBlock(before))
|
|
284
256
|
throw new SessionBranchError("所选助手消息块不是文本或思考。", "INVALID_BOUNDARY");
|
|
285
|
-
const blockKind: EditableBlockKind =
|
|
286
|
-
before.type === "reasoning" ? "assistant.reasoning" : "assistant.response";
|
|
287
257
|
if (turn.user === undefined)
|
|
288
258
|
throw new SessionBranchError("所选助手消息没有可重建的用户输入。", "INVALID_BOUNDARY");
|
|
289
259
|
return {
|
|
290
260
|
anchorSeq: turn.startSeq,
|
|
291
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
292
|
-
operation: "edit",
|
|
293
|
-
cascade: operation.cascade,
|
|
294
|
-
targetTurn: turn.turn,
|
|
295
|
-
targetEventSeq: event.seq,
|
|
296
|
-
targetBlockIndex: operation.blockIndex,
|
|
297
|
-
blockKind,
|
|
298
|
-
before: before.text,
|
|
299
|
-
after: operation.text,
|
|
300
|
-
}),
|
|
301
261
|
manualTurn: {
|
|
302
262
|
turn: turn.turn,
|
|
303
263
|
user: cloneUser(turn.user.data),
|
|
@@ -315,12 +275,6 @@ export function retryPlan(operation: RetryOperation, turns: readonly ClosedTurn[
|
|
|
315
275
|
throw new SessionBranchError("所选回合没有可重放的用户输入。", "INVALID_BOUNDARY");
|
|
316
276
|
return {
|
|
317
277
|
anchorSeq: turn.startSeq,
|
|
318
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
319
|
-
operation: "retry",
|
|
320
|
-
cascade: operation.cascade,
|
|
321
|
-
targetTurn: turn.turn,
|
|
322
|
-
targetEventSeq: turn.user.seq,
|
|
323
|
-
}),
|
|
324
278
|
queuedUsers:
|
|
325
279
|
operation.cascade === "preserve"
|
|
326
280
|
? downstreamUsers(turns, turnIndex)
|
|
@@ -343,12 +297,6 @@ export function rerollPlan(
|
|
|
343
297
|
if (target === undefined) continue;
|
|
344
298
|
return {
|
|
345
299
|
anchorSeq: turn.startSeq,
|
|
346
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
347
|
-
operation: "reroll",
|
|
348
|
-
cascade: "truncate",
|
|
349
|
-
targetTurn: turn.turn,
|
|
350
|
-
targetEventSeq: target.seq,
|
|
351
|
-
}),
|
|
352
300
|
queuedUsers: turn.users.map((user) => cloneUser(user.data)),
|
|
353
301
|
targetSeq: target.seq,
|
|
354
302
|
};
|
package/src/shared.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
2
|
-
import type {
|
|
3
|
-
import type { VersionOperation } from "@morlay/session-branch";
|
|
2
|
+
import type { CascadePolicy } from "@morlay/session-branch";
|
|
4
3
|
|
|
5
4
|
export const SESSION_EDITOR_PATH = "/session-editor";
|
|
6
5
|
|
|
@@ -79,97 +78,3 @@ export interface RetryableTurn {
|
|
|
79
78
|
time: number;
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
export interface VersionSummary {
|
|
83
|
-
sessionId: string;
|
|
84
|
-
parentSessionId?: string;
|
|
85
|
-
effectId?: string;
|
|
86
|
-
inverseSessionId?: string;
|
|
87
|
-
createdAt: number;
|
|
88
|
-
depth: number;
|
|
89
|
-
current: boolean;
|
|
90
|
-
onCurrentEffectPath: boolean;
|
|
91
|
-
operation?: VersionOperation;
|
|
92
|
-
cascade?: CascadePolicy;
|
|
93
|
-
targetTurn?: number;
|
|
94
|
-
blockKind?: EditableMessageBlock["kind"];
|
|
95
|
-
before?: string;
|
|
96
|
-
after?: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface SessionEditorTimeline {
|
|
100
|
-
sessionId: string;
|
|
101
|
-
messages: EditableMessageBlock[];
|
|
102
|
-
retryableTurns: RetryableTurn[];
|
|
103
|
-
versions: VersionSummary[];
|
|
104
|
-
|
|
105
|
-
undoStack: string[];
|
|
106
|
-
|
|
107
|
-
redoSessionIds: string[];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function toTimelinePayload(
|
|
111
|
-
sessionId: SessionId,
|
|
112
|
-
timeline: BranchTimeline,
|
|
113
|
-
messages: EditableMessageBlock[],
|
|
114
|
-
retryableTurns: RetryableTurn[],
|
|
115
|
-
): SessionEditorTimeline {
|
|
116
|
-
const currentPath = new Set<string>();
|
|
117
|
-
for (const node of timeline.nodes) currentPath.add(String(node.sessionId));
|
|
118
|
-
const versions: VersionSummary[] = timeline.nodes.map((node) => ({
|
|
119
|
-
sessionId: String(node.sessionId),
|
|
120
|
-
...(node.parentSessionId === undefined
|
|
121
|
-
? {}
|
|
122
|
-
: { parentSessionId: String(node.parentSessionId) }),
|
|
123
|
-
...(node.effect === undefined
|
|
124
|
-
? {}
|
|
125
|
-
: {
|
|
126
|
-
effectId: node.effect.id,
|
|
127
|
-
inverseSessionId: String(node.inverseSessionId),
|
|
128
|
-
operation: node.effect.operation,
|
|
129
|
-
cascade: node.effect.cascade,
|
|
130
|
-
targetTurn: node.effect.targetTurn,
|
|
131
|
-
...(node.effect.blockKind === undefined ? {} : { blockKind: node.effect.blockKind }),
|
|
132
|
-
...(node.effect.before === undefined ? {} : { before: node.effect.before }),
|
|
133
|
-
...(node.effect.after === undefined ? {} : { after: node.effect.after }),
|
|
134
|
-
}),
|
|
135
|
-
createdAt: node.createdAt,
|
|
136
|
-
depth: depthOf(timeline, node.sessionId),
|
|
137
|
-
current: String(node.sessionId) === String(sessionId),
|
|
138
|
-
onCurrentEffectPath: currentPath.has(String(node.sessionId)),
|
|
139
|
-
}));
|
|
140
|
-
const versionsById = new Map(versions.map((version) => [version.sessionId, version]));
|
|
141
|
-
const undoStack: string[] = [];
|
|
142
|
-
let cursor = versionsById.get(String(sessionId));
|
|
143
|
-
while (cursor?.inverseSessionId !== undefined) {
|
|
144
|
-
if (undoStack.includes(cursor.inverseSessionId)) break;
|
|
145
|
-
undoStack.push(cursor.inverseSessionId);
|
|
146
|
-
cursor = versionsById.get(cursor.inverseSessionId);
|
|
147
|
-
}
|
|
148
|
-
const redoSessionIds = versions
|
|
149
|
-
.filter((version) => version.inverseSessionId === String(sessionId))
|
|
150
|
-
.map((version) => version.sessionId);
|
|
151
|
-
return {
|
|
152
|
-
sessionId: String(sessionId),
|
|
153
|
-
messages,
|
|
154
|
-
retryableTurns,
|
|
155
|
-
versions,
|
|
156
|
-
undoStack,
|
|
157
|
-
redoSessionIds,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function depthOf(
|
|
162
|
-
timeline: BranchTimeline,
|
|
163
|
-
sessionId: import("@deepseek-ai/dsh-session").SessionId,
|
|
164
|
-
): number {
|
|
165
|
-
const byId = new Map(timeline.nodes.map((node) => [String(node.sessionId), node]));
|
|
166
|
-
let depth = 0;
|
|
167
|
-
let cursor = byId.get(String(sessionId));
|
|
168
|
-
const seen = new Set<string>();
|
|
169
|
-
while (cursor?.parentSessionId !== undefined && !seen.has(String(cursor.sessionId))) {
|
|
170
|
-
seen.add(String(cursor.sessionId));
|
|
171
|
-
depth += 1;
|
|
172
|
-
cursor = byId.get(String(cursor.parentSessionId));
|
|
173
|
-
}
|
|
174
|
-
return depth;
|
|
175
|
-
}
|
package/src/testing.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
type SessionHeader,
|
|
10
10
|
} from "@deepseek-ai/dsh-session";
|
|
11
11
|
import SessionProjectionRegistry from "@deepseek-ai/dsh-session-projection";
|
|
12
|
-
import { type BranchTimeline } from "@morlay/session-branch";
|
|
13
12
|
import SessionPersistenceSqlite from "@morlay/session-rdb";
|
|
14
13
|
import { parseJsonlArtifact } from "@morlay/session-rdb/artifact";
|
|
15
14
|
import { EmptySettings, meta, oneTurnLog } from "@morlay/session-rdb/testing";
|
|
@@ -17,7 +16,6 @@ import { SESSION_EDITOR_PATH } from "./shared.ts";
|
|
|
17
16
|
import { SessionEditor } from "@morlay/ui-conversation-message-actions";
|
|
18
17
|
|
|
19
18
|
export {
|
|
20
|
-
BranchTimeline,
|
|
21
19
|
SESSION_EDITOR_PATH,
|
|
22
20
|
Session,
|
|
23
21
|
SessionIdBrand,
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
2
|
-
import type {
|
|
2
|
+
import type { CascadePolicy } from "@morlay/session-branch";
|
|
3
3
|
|
|
4
4
|
export interface EditOperation {
|
|
5
5
|
action: "edit";
|
|
@@ -61,8 +61,6 @@ export interface SessionEditorResult {
|
|
|
61
61
|
live?: boolean;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export type SessionEditorTimeline = BranchTimeline;
|
|
65
|
-
|
|
66
64
|
export interface EditableMessageBlock {
|
|
67
65
|
key: string;
|
|
68
66
|
|
package/dist/shared-LK3nImWW.mjs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
//#region src/shared.ts
|
|
2
|
-
const SESSION_EDITOR_PATH = "/session-editor";
|
|
3
|
-
function toTimelinePayload(sessionId, timeline, messages, retryableTurns) {
|
|
4
|
-
const currentPath = /* @__PURE__ */ new Set();
|
|
5
|
-
for (const node of timeline.nodes) currentPath.add(String(node.sessionId));
|
|
6
|
-
const versions = timeline.nodes.map((node) => ({
|
|
7
|
-
sessionId: String(node.sessionId),
|
|
8
|
-
...node.parentSessionId === void 0 ? {} : { parentSessionId: String(node.parentSessionId) },
|
|
9
|
-
...node.effect === void 0 ? {} : {
|
|
10
|
-
effectId: node.effect.id,
|
|
11
|
-
inverseSessionId: String(node.inverseSessionId),
|
|
12
|
-
operation: node.effect.operation,
|
|
13
|
-
cascade: node.effect.cascade,
|
|
14
|
-
targetTurn: node.effect.targetTurn,
|
|
15
|
-
...node.effect.blockKind === void 0 ? {} : { blockKind: node.effect.blockKind },
|
|
16
|
-
...node.effect.before === void 0 ? {} : { before: node.effect.before },
|
|
17
|
-
...node.effect.after === void 0 ? {} : { after: node.effect.after }
|
|
18
|
-
},
|
|
19
|
-
createdAt: node.createdAt,
|
|
20
|
-
depth: depthOf(timeline, node.sessionId),
|
|
21
|
-
current: String(node.sessionId) === String(sessionId),
|
|
22
|
-
onCurrentEffectPath: currentPath.has(String(node.sessionId))
|
|
23
|
-
}));
|
|
24
|
-
const versionsById = new Map(versions.map((version) => [version.sessionId, version]));
|
|
25
|
-
const undoStack = [];
|
|
26
|
-
let cursor = versionsById.get(String(sessionId));
|
|
27
|
-
while (cursor?.inverseSessionId !== void 0) {
|
|
28
|
-
if (undoStack.includes(cursor.inverseSessionId)) break;
|
|
29
|
-
undoStack.push(cursor.inverseSessionId);
|
|
30
|
-
cursor = versionsById.get(cursor.inverseSessionId);
|
|
31
|
-
}
|
|
32
|
-
const redoSessionIds = versions.filter((version) => version.inverseSessionId === String(sessionId)).map((version) => version.sessionId);
|
|
33
|
-
return {
|
|
34
|
-
sessionId: String(sessionId),
|
|
35
|
-
messages,
|
|
36
|
-
retryableTurns,
|
|
37
|
-
versions,
|
|
38
|
-
undoStack,
|
|
39
|
-
redoSessionIds
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function depthOf(timeline, sessionId) {
|
|
43
|
-
const byId = new Map(timeline.nodes.map((node) => [String(node.sessionId), node]));
|
|
44
|
-
let depth = 0;
|
|
45
|
-
let cursor = byId.get(String(sessionId));
|
|
46
|
-
const seen = /* @__PURE__ */ new Set();
|
|
47
|
-
while (cursor?.parentSessionId !== void 0 && !seen.has(String(cursor.sessionId))) {
|
|
48
|
-
seen.add(String(cursor.sessionId));
|
|
49
|
-
depth += 1;
|
|
50
|
-
cursor = byId.get(String(cursor.parentSessionId));
|
|
51
|
-
}
|
|
52
|
-
return depth;
|
|
53
|
-
}
|
|
54
|
-
//#endregion
|
|
55
|
-
export { toTimelinePayload as n, SESSION_EDITOR_PATH as t };
|