@morlay/session-branch 0.0.10-alpha.3 → 0.0.10-alpha.5

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @morlay/session-branch
2
2
 
3
3
  分支式会话编辑的**契约层**:定义数据层分支原语 `SessionBranchProvider`
4
- (rewind / forkFrom / readBranchPrefix)、高层服务 `SessionBranch`
5
- (`ctx.sessionBranch`)与共享的版本树投影 `buildTimeline`。
4
+ (rewind / forkFrom / readBranchPrefix)与高层服务 `SessionBranch`
5
+ (`ctx.sessionBranch`)。
6
6
 
7
7
  跨包术语见根 [`.agents/CONTEXT.md`](../../../.agents/CONTEXT.md),本包的决策见 [`.agents/adrs/`](./.agents/adrs)。
8
8
 
@@ -38,11 +38,8 @@ interface SessionBranchProvider {
38
38
  (内存 log 截断 + 派生缓存复位 + handle cursor / 继承前缀对齐,详见
39
39
  `@morlay/session-rdb` 的 [分支能力](../session-rdb/.agents/designs/20260917-分支能力.md))。
40
40
 
41
- ## 版本树
41
+ ## 版本效果(历史形状)
42
42
 
43
- `buildTimeline(snapshots, readOwnEvents, sessionId)` 从会话快照(header
44
- lineage)+ 每会话自有后缀(`seq >= seedLength` 的 `session-branch/version`
45
- 事件)投影完整版本树。
46
-
47
- 版本效果事件携带 `ignorable: true` 并**原样落库**,因此 cold 会话也能恢复效果详情
48
- (见 [ADR-版本效果以ignorable事件原样落库](./.agents/adrs/20260917-版本效果以ignorable事件原样落库.md))。
43
+ `session-branch/version` 事件的类型定义仍在本包(`types.ts`),用于识别旧会话里
44
+ 已落库的历史事件;**写侧与读侧都已删除**(不再产出新事件、没有读者),理由见
45
+ [ADR-删除版本树投影并停止写版本效果](./.agents/adrs/20260920-删除版本树投影并停止写版本效果.md)。
package/dist/index.d.mts CHANGED
@@ -1,9 +1,7 @@
1
1
  import { Context, Service } from "@deepseek-ai/cordis";
2
- import { SessionEvent, SessionEventMap, SessionId } from "@deepseek-ai/dsh-session";
2
+ import { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
3
3
  import { SessionPersistenceSnapshot } from "@deepseek-ai/dsh-session-persistence";
4
4
  //#region src/types.d.ts
5
- type _BranchKeyCheck = "session-branch/version" extends keyof SessionEventMap ? true : false;
6
- declare const _branchKeyVisible: _BranchKeyCheck;
7
5
  declare const SESSION_BRANCH_VERSION_SCHEMA = 1;
8
6
  type CascadePolicy = "truncate" | "preserve";
9
7
  type VersionOperation = "edit" | "reroll" | "retry" | "fork" | "rewind";
@@ -51,18 +49,6 @@ interface ForkFromOptions {
51
49
  childSessionId?: SessionId;
52
50
  meta?: BranchForkMeta;
53
51
  }
54
- interface BranchVersionNode {
55
- sessionId: SessionId;
56
- parentSessionId?: SessionId;
57
- seedLength: number;
58
- createdAt: number;
59
- effect?: SessionBranchEffect;
60
- inverseSessionId?: SessionId;
61
- }
62
- interface BranchTimeline {
63
- root: BranchVersionNode;
64
- nodes: BranchVersionNode[];
65
- }
66
52
  type SessionBranchErrorCode = "SESSION_NOT_FOUND" | "INVALID_BOUNDARY" | "OPEN_TURN" | "FORK_UNAVAILABLE" | "REWIND_CONFLICT";
67
53
  declare class SessionBranchError extends Error {
68
54
  readonly code: SessionBranchErrorCode;
@@ -95,16 +81,8 @@ declare abstract class SessionBranch extends Service {
95
81
  abstract readBranchPrefix(id: SessionId, atSeq?: number, mode?: BranchAnchorMode, signal?: AbortSignal): Promise<BranchBoundary>;
96
82
  abstract forkFrom(sourceId: SessionId, options?: ForkFromOptions, signal?: AbortSignal): Promise<SessionId>;
97
83
  abstract rewind(id: SessionId, toBoundary: number, signal?: AbortSignal): Promise<SessionPersistenceSnapshot>;
98
- abstract timeline(sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline>;
99
84
  }
100
85
  //#endregion
101
- //#region src/timeline.d.ts
102
- type BranchSnapshot = SessionPersistenceSnapshot & {
103
- inheritedEventCount: number;
104
- };
105
- type OwnEventsReader = (id: SessionId, fromSeq: number, signal?: AbortSignal) => Promise<readonly import("@deepseek-ai/dsh-session").SessionEvent[]>;
106
- declare function buildTimeline(snapshots: readonly BranchSnapshot[], readOwnEvents: OwnEventsReader, sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline>;
107
- //#endregion
108
86
  //#region src/balance.d.ts
109
87
  interface BalanceRewindPrefixOptions {
110
88
  keepOpenTail?: boolean;
@@ -120,4 +98,4 @@ declare module "@deepseek-ai/cordis" {
120
98
  }
121
99
  declare function apply(_ctx: Context): void;
122
100
  //#endregion
123
- export { type BranchAnchorMode, BranchBoundary, BranchForkMeta, BranchTimeline, BranchVersionNode, CascadePolicy, EditableBlockKind, ForkFromOptions, type OwnEventsReader, SESSION_BRANCH_VERSION_SCHEMA, SessionBranch, SessionBranchEffect, SessionBranchError, SessionBranchErrorCode, SessionBranchInverse, type SessionBranchProvider, SessionBranchVersionEvent, SessionBranchVersionEventEnvelope, VersionOperation, _branchKeyVisible, apply, balanceRewindPrefix, buildTimeline, isSessionBranchVersionEvent, rewindKeepLength };
101
+ export { type BranchAnchorMode, BranchBoundary, BranchForkMeta, CascadePolicy, EditableBlockKind, ForkFromOptions, SESSION_BRANCH_VERSION_SCHEMA, SessionBranch, SessionBranchEffect, SessionBranchError, SessionBranchErrorCode, SessionBranchInverse, type SessionBranchProvider, SessionBranchVersionEvent, SessionBranchVersionEventEnvelope, VersionOperation, apply, balanceRewindPrefix, isSessionBranchVersionEvent, rewindKeepLength };
package/dist/index.mjs CHANGED
@@ -6,82 +6,6 @@ var SessionBranch = class extends Service {
6
6
  }
7
7
  };
8
8
  //#endregion
9
- //#region src/types.ts
10
- const _branchKeyVisible = true;
11
- const SESSION_BRANCH_VERSION_SCHEMA = 1;
12
- var SessionBranchError = class extends Error {
13
- code;
14
- constructor(message, code) {
15
- super(message);
16
- this.name = "SessionBranchError";
17
- this.code = code;
18
- }
19
- };
20
- function isSessionBranchVersionEvent(event) {
21
- return event.type === "session-branch/version" && event.data.schemaVersion === 1;
22
- }
23
- //#endregion
24
- //#region src/timeline.ts
25
- async function buildTimeline(snapshots, readOwnEvents, sessionId, signal) {
26
- const byId = new Map(snapshots.map((snapshot) => [snapshot.header.id, snapshot]));
27
- const ancestors = [];
28
- let cursor = sessionId;
29
- const seen = /* @__PURE__ */ new Set();
30
- while (cursor !== void 0) {
31
- if (seen.has(cursor)) throw new SessionBranchError("lineage contains a cycle", "INVALID_BOUNDARY");
32
- seen.add(cursor);
33
- ancestors.push(cursor);
34
- cursor = byId.get(cursor)?.header.parentSession;
35
- }
36
- const rootId = ancestors.at(-1);
37
- if (rootId === void 0) throw new SessionBranchError(`session "${sessionId}" is not persisted`, "SESSION_NOT_FOUND");
38
- const ordered = [];
39
- const queue = [rootId];
40
- while (queue.length > 0) {
41
- const id = queue.shift();
42
- if (id === void 0) continue;
43
- ordered.push(id);
44
- const children = snapshots.filter((snapshot) => snapshot.header.parentSession === id).sort((left, right) => left.header.createdAt - right.header.createdAt || String(left.header.id).localeCompare(String(right.header.id))).map((snapshot) => snapshot.header.id);
45
- queue.push(...children);
46
- }
47
- const nodes = [];
48
- const effectIds = /* @__PURE__ */ new Set();
49
- for (const id of ordered) {
50
- const snapshot = byId.get(id);
51
- if (snapshot === void 0) continue;
52
- const header = snapshot.header;
53
- const node = {
54
- sessionId: header.id,
55
- ...header.parentSession === void 0 ? {} : { parentSessionId: header.parentSession },
56
- seedLength: snapshot.inheritedEventCount ?? 0,
57
- createdAt: header.createdAt
58
- };
59
- if (header.parentSession !== void 0) {
60
- const events = await readOwnEvents(header.id, node.seedLength, signal);
61
- let version;
62
- for (const event of events) if (isSessionBranchVersionEvent(event)) {
63
- if (version !== void 0) throw new SessionBranchError(`session ${header.id} carries multiple own version effects`, "INVALID_BOUNDARY");
64
- version = event;
65
- }
66
- if (version !== void 0) {
67
- const data = version.data;
68
- if (data.inverse.kind !== "restore-version" || data.inverse.sessionId !== header.parentSession) throw new SessionBranchError(`session ${header.id} version inverse does not match its parent`, "INVALID_BOUNDARY");
69
- if (effectIds.has(data.effect.id)) throw new SessionBranchError(`version effect ${data.effect.id} is duplicated`, "INVALID_BOUNDARY");
70
- effectIds.add(data.effect.id);
71
- node.effect = data.effect;
72
- node.inverseSessionId = data.inverse.sessionId;
73
- }
74
- }
75
- nodes.push(node);
76
- }
77
- const root = nodes.find((node) => node.parentSessionId === void 0);
78
- if (root === void 0) throw new SessionBranchError(`session "${sessionId}" lineage has no root`, "SESSION_NOT_FOUND");
79
- return {
80
- root,
81
- nodes
82
- };
83
- }
84
- //#endregion
85
9
  //#region src/balance.ts
86
10
  function balanceRewindPrefix(events, options = {}) {
87
11
  let keep = events.length;
@@ -131,7 +55,21 @@ function rewindKeepLength(types, rawKeepLength) {
131
55
  return Math.max(0, rawKeepLength - types.length + relativeKeep);
132
56
  }
133
57
  //#endregion
58
+ //#region src/types.ts
59
+ const SESSION_BRANCH_VERSION_SCHEMA = 1;
60
+ var SessionBranchError = class extends Error {
61
+ code;
62
+ constructor(message, code) {
63
+ super(message);
64
+ this.name = "SessionBranchError";
65
+ this.code = code;
66
+ }
67
+ };
68
+ function isSessionBranchVersionEvent(event) {
69
+ return event.type === "session-branch/version" && event.data.schemaVersion === 1;
70
+ }
71
+ //#endregion
134
72
  //#region src/index.ts
135
73
  function apply(_ctx) {}
136
74
  //#endregion
137
- export { SESSION_BRANCH_VERSION_SCHEMA, SessionBranch, SessionBranchError, _branchKeyVisible, apply, balanceRewindPrefix, buildTimeline, isSessionBranchVersionEvent, rewindKeepLength };
75
+ export { SESSION_BRANCH_VERSION_SCHEMA, SessionBranch, SessionBranchError, apply, balanceRewindPrefix, isSessionBranchVersionEvent, rewindKeepLength };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/session-branch",
3
- "version": "0.0.10-alpha.3",
3
+ "version": "0.0.10-alpha.5",
4
4
  "description": "Provider abstraction + high-level service for rewind / retry / fork over DeepSeek Harness event-sourced sessions.",
5
5
  "keywords": [
6
6
  "dsh",
package/src/branch.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Service } from "@deepseek-ai/cordis";
2
2
  import type { SessionId } from "@deepseek-ai/dsh-session";
3
3
  import type { SessionPersistenceSnapshot } from "@deepseek-ai/dsh-session-persistence";
4
- import type { BranchBoundary, BranchTimeline, ForkFromOptions } from "./types.ts";
4
+ import type { BranchBoundary, ForkFromOptions } from "./types.ts";
5
5
 
6
6
  export abstract class SessionBranch extends Service {
7
7
  constructor(ctx: import("@deepseek-ai/cordis").Context) {
@@ -26,6 +26,4 @@ export abstract class SessionBranch extends Service {
26
26
  toBoundary: number,
27
27
  signal?: AbortSignal,
28
28
  ): Promise<SessionPersistenceSnapshot>;
29
-
30
- abstract timeline(sessionId: SessionId, signal?: AbortSignal): Promise<BranchTimeline>;
31
29
  }
package/src/index.ts CHANGED
@@ -3,8 +3,6 @@ import { SessionBranch } from "./branch.ts";
3
3
 
4
4
  export { SessionBranch } from "./branch.ts";
5
5
  export type { SessionBranchProvider, BranchAnchorMode } from "./provider.ts";
6
- export { buildTimeline } from "./timeline.ts";
7
- export type { OwnEventsReader } from "./timeline.ts";
8
6
  export { balanceRewindPrefix, rewindKeepLength } from "./balance.ts";
9
7
  export * from "./types.ts";
10
8
 
package/src/types.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import type { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
2
- import type { SessionEventMap } from "@deepseek-ai/dsh-session";
3
-
4
- type _BranchKeyCheck = "session-branch/version" extends keyof SessionEventMap ? true : false;
5
- export const _branchKeyVisible: _BranchKeyCheck = true as const;
6
2
 
3
+ // `session-branch/version` 的**历史形状**:写侧(就地编辑产出)与读侧(版本树投影)都已删除
4
+ // (见 ADR 版本效果停止落库 / 版本树投影删除)。这里的定义只用于识别旧数据里已落库的
5
+ // ignorable 事件,不再有新事件产生、也没有读者。
7
6
  export const SESSION_BRANCH_VERSION_SCHEMA = 1;
8
7
 
9
8
  export type CascadePolicy = "truncate" | "preserve";
@@ -71,23 +70,6 @@ export interface ForkFromOptions {
71
70
  meta?: BranchForkMeta;
72
71
  }
73
72
 
74
- export interface BranchVersionNode {
75
- sessionId: SessionId;
76
- parentSessionId?: SessionId;
77
-
78
- seedLength: number;
79
- createdAt: number;
80
-
81
- effect?: SessionBranchEffect;
82
-
83
- inverseSessionId?: SessionId;
84
- }
85
-
86
- export interface BranchTimeline {
87
- root: BranchVersionNode;
88
- nodes: BranchVersionNode[];
89
- }
90
-
91
73
  export type SessionBranchErrorCode =
92
74
  | "SESSION_NOT_FOUND"
93
75
  | "INVALID_BOUNDARY"
package/src/timeline.ts DELETED
@@ -1,120 +0,0 @@
1
- import type { SessionId } from "@deepseek-ai/dsh-session";
2
- import type { SessionPersistenceSnapshot } from "@deepseek-ai/dsh-session-persistence";
3
- import {
4
- SessionBranchError,
5
- isSessionBranchVersionEvent,
6
- type BranchTimeline,
7
- type BranchVersionNode,
8
- type SessionBranchVersionEventEnvelope,
9
- } from "./types.ts";
10
-
11
- export type BranchSnapshot = SessionPersistenceSnapshot & {
12
- inheritedEventCount: number;
13
- };
14
-
15
- export type OwnEventsReader = (
16
- id: SessionId,
17
- fromSeq: number,
18
- signal?: AbortSignal,
19
- ) => Promise<readonly import("@deepseek-ai/dsh-session").SessionEvent[]>;
20
-
21
- export async function buildTimeline(
22
- snapshots: readonly BranchSnapshot[],
23
- readOwnEvents: OwnEventsReader,
24
- sessionId: SessionId,
25
- signal?: AbortSignal,
26
- ): Promise<BranchTimeline> {
27
- const byId = new Map(snapshots.map((snapshot) => [snapshot.header.id, snapshot] as const));
28
-
29
- const ancestors: SessionId[] = [];
30
- let cursor: SessionId | undefined = sessionId;
31
- const seen = new Set<SessionId>();
32
- while (cursor !== undefined) {
33
- if (seen.has(cursor))
34
- throw new SessionBranchError("lineage contains a cycle", "INVALID_BOUNDARY");
35
- seen.add(cursor);
36
- ancestors.push(cursor);
37
- const snapshot = byId.get(cursor);
38
- cursor = snapshot?.header.parentSession;
39
- }
40
- const rootId = ancestors.at(-1);
41
- if (rootId === undefined) {
42
- throw new SessionBranchError(`session "${sessionId}" is not persisted`, "SESSION_NOT_FOUND");
43
- }
44
-
45
- const ordered: SessionId[] = [];
46
- const queue: SessionId[] = [rootId];
47
- while (queue.length > 0) {
48
- const id = queue.shift();
49
- if (id === undefined) continue;
50
- ordered.push(id);
51
- const children = snapshots
52
- .filter((snapshot) => snapshot.header.parentSession === id)
53
- .sort(
54
- (left, right) =>
55
- left.header.createdAt - right.header.createdAt ||
56
- String(left.header.id).localeCompare(String(right.header.id)),
57
- )
58
- .map((snapshot) => snapshot.header.id);
59
- queue.push(...children);
60
- }
61
-
62
- const nodes: BranchVersionNode[] = [];
63
- const effectIds = new Set<string>();
64
- for (const id of ordered) {
65
- const snapshot = byId.get(id);
66
- if (snapshot === undefined) continue;
67
- const header = snapshot.header;
68
- const node: BranchVersionNode = {
69
- sessionId: header.id,
70
- ...(header.parentSession === undefined ? {} : { parentSessionId: header.parentSession }),
71
- seedLength: snapshot.inheritedEventCount ?? 0,
72
- createdAt: header.createdAt,
73
- };
74
-
75
- if (header.parentSession !== undefined) {
76
- const events = await readOwnEvents(header.id, node.seedLength, signal);
77
-
78
- let version: SessionBranchVersionEventEnvelope | undefined;
79
- for (const event of events) {
80
- if (isSessionBranchVersionEvent(event)) {
81
- if (version !== undefined) {
82
- throw new SessionBranchError(
83
- `session ${header.id} carries multiple own version effects`,
84
- "INVALID_BOUNDARY",
85
- );
86
- }
87
- version = event;
88
- }
89
- }
90
- if (version !== undefined) {
91
- const data = version.data;
92
- if (
93
- data.inverse.kind !== "restore-version" ||
94
- data.inverse.sessionId !== header.parentSession
95
- ) {
96
- throw new SessionBranchError(
97
- `session ${header.id} version inverse does not match its parent`,
98
- "INVALID_BOUNDARY",
99
- );
100
- }
101
- if (effectIds.has(data.effect.id)) {
102
- throw new SessionBranchError(
103
- `version effect ${data.effect.id} is duplicated`,
104
- "INVALID_BOUNDARY",
105
- );
106
- }
107
- effectIds.add(data.effect.id);
108
- node.effect = data.effect;
109
- node.inverseSessionId = data.inverse.sessionId;
110
- }
111
- }
112
- nodes.push(node);
113
- }
114
-
115
- const root = nodes.find((node) => node.parentSessionId === undefined);
116
- if (root === undefined) {
117
- throw new SessionBranchError(`session "${sessionId}" lineage has no root`, "SESSION_NOT_FOUND");
118
- }
119
- return { root, nodes };
120
- }