@robiteame/dsh-pi-agent-session-tree 0.1.0

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.
@@ -0,0 +1,2 @@
1
+ declare const TYPERT_REMOTE: Record<string, unknown>
2
+ export default TYPERT_REMOTE
@@ -0,0 +1,247 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
2
+ import { z } from 'zod'
3
+
4
+ const JsonValueRemoteCodec$schema = z.union([z.literal(null), z.string(), z.number(), z.literal(false), z.literal(true), z.array(z.lazy(() => JsonValueRemoteCodec$schema)), z.record(z.string(), z.lazy(() => JsonValueRemoteCodec$schema))])
5
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_parameter_0$schema = z.intersection(z.string(), z.unknown())
6
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_parameter_1$schema = z.string()
7
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_parameter_2$schema = z.string()
8
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_result$schema = z.object({
9
+ 'cursor': z.string(),
10
+ 'branch': z.string(),
11
+ 'forkCount': z.number(),
12
+ })
13
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_jump_parameter_0$schema = z.intersection(z.string(), z.unknown())
14
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_jump_parameter_1$schema = z.union([z.literal(null), z.string()])
15
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_jump_result$schema = z.object({
16
+ 'cursor': z.union([z.literal(null), z.string()]).readonly(),
17
+ 'messages': z.array(z.object({
18
+ 'role': z.union([z.literal("system"), z.literal("user"), z.literal("assistant"), z.literal("tool")]).readonly(),
19
+ 'content': z.string().readonly(),
20
+ 'name': z.string().readonly().optional(),
21
+ 'toolCallId': z.string().readonly().optional(),
22
+ })).readonly(),
23
+ })
24
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_list_parameter_0$schema = z.intersection(z.string(), z.unknown())
25
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_list_result$schema = z.object({
26
+ 'sessionId': z.intersection(z.string(), z.unknown()).readonly(),
27
+ 'cursor': z.union([z.literal(null), z.string()]).readonly(),
28
+ 'activeBranch': z.string().readonly(),
29
+ 'selectedNodeId': z.union([z.literal(null), z.string()]).readonly().optional(),
30
+ 'branchHeads': z.record(z.string(), z.string()).readonly().optional(),
31
+ 'nodes': z.array(z.object({
32
+ 'nodeId': z.string().readonly(),
33
+ 'parentId': z.union([z.literal(null), z.string()]).readonly(),
34
+ 'forkCount': z.number().readonly().optional(),
35
+ 'type': z.union([z.literal("message"), z.literal("tool_call"), z.literal("tool_result"), z.literal("model_change"), z.literal("compaction"), z.literal("branch_summary"), z.literal("custom")]).readonly().optional(),
36
+ 'branch': z.string().readonly(),
37
+ 'summary': z.string().readonly(),
38
+ 'createdAt': z.string().readonly(),
39
+ 'message': z.object({
40
+ 'role': z.union([z.literal("system"), z.literal("user"), z.literal("assistant"), z.literal("tool")]).readonly(),
41
+ 'content': z.string().readonly(),
42
+ 'name': z.string().readonly().optional(),
43
+ 'toolCallId': z.string().readonly().optional(),
44
+ }).readonly().optional(),
45
+ 'content': z.array(z.union([z.object({
46
+ 'type': z.literal("text").readonly(),
47
+ 'text': z.string().readonly(),
48
+ }), z.object({
49
+ 'type': z.literal("tool_call").readonly(),
50
+ 'id': z.string().readonly(),
51
+ 'name': z.string().readonly(),
52
+ 'arguments': z.union([z.literal(null), z.string(), z.number(), z.literal(false), z.literal(true), z.array(z.lazy(() => JsonValueRemoteCodec$schema)), z.record(z.string(), z.lazy(() => JsonValueRemoteCodec$schema))]).readonly(),
53
+ }), z.object({
54
+ 'type': z.literal("tool_result").readonly(),
55
+ 'toolCallId': z.string().readonly(),
56
+ 'content': z.string().readonly(),
57
+ 'isError': z.boolean().readonly().optional(),
58
+ }), z.object({
59
+ 'type': z.literal("reasoning").readonly(),
60
+ 'text': z.string().readonly(),
61
+ })])).readonly().optional(),
62
+ 'model': z.string().readonly().optional(),
63
+ 'usage': z.record(z.string(), z.union([z.literal(null), z.string(), z.number(), z.literal(false), z.literal(true), z.array(z.lazy(() => JsonValueRemoteCodec$schema)), z.record(z.string(), z.lazy(() => JsonValueRemoteCodec$schema))])).readonly().optional(),
64
+ 'cost': z.number().readonly().optional(),
65
+ 'error': z.string().readonly().optional(),
66
+ 'metadata': z.record(z.string(), z.union([z.literal(null), z.string(), z.number(), z.literal(false), z.literal(true), z.array(z.lazy(() => JsonValueRemoteCodec$schema)), z.record(z.string(), z.lazy(() => JsonValueRemoteCodec$schema))])).readonly().optional(),
67
+ })).readonly(),
68
+ 'branches': z.array(z.object({
69
+ 'name': z.string().readonly(),
70
+ 'headId': z.string().readonly(),
71
+ 'nodeIds': z.array(z.string()).readonly(),
72
+ })).readonly(),
73
+ })
74
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_session_parameter_0$schema = z.intersection(z.string(), z.unknown())
75
+ const _robiteame_dsh_pi_agent_session_tree_sessionTree_session_result$schema = z.object({
76
+ 'branchHeads': z.record(z.string(), z.string()).readonly().optional(),
77
+ 'sessionId': z.intersection(z.string(), z.unknown()).readonly(),
78
+ 'nodeCount': z.number().readonly(),
79
+ 'messageCount': z.number().readonly(),
80
+ 'branchCount': z.number().readonly(),
81
+ 'cursor': z.union([z.literal(null), z.string()]).readonly(),
82
+ 'activeBranch': z.string().readonly(),
83
+ 'selectedNodeId': z.union([z.literal(null), z.string()]).readonly().optional(),
84
+ 'currentPathLength': z.number().readonly(),
85
+ 'usage': z.record(z.string(), z.number()).readonly().optional(),
86
+ 'tokenCount': z.number().readonly().optional(),
87
+ 'cost': z.number().readonly().optional(),
88
+ 'snapshotVersion': z.literal(1).readonly(),
89
+ })
90
+
91
+ export const TYPERT_REMOTE = {
92
+ package: '@robiteame/dsh-pi-agent-session-tree',
93
+ descriptors: [
94
+ {
95
+ id: '@robiteame/dsh-pi-agent-session-tree#sessionTree/fork',
96
+ service: 'sessionTree',
97
+ namespace: 'sessionTree',
98
+ method: 'fork',
99
+ invocation: { kind: 'direct' },
100
+ scope: {
101
+ context: 'agent',
102
+ wire: 'agentId',
103
+ },
104
+ parameters: [
105
+ {
106
+ name: 'agent',
107
+ wire: 'agentId',
108
+ source: 'lookup',
109
+ lookup: 'agent',
110
+ codec: {
111
+ mode: 'strict',
112
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
113
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_parameter_0$schema,
114
+ },
115
+ },
116
+ {
117
+ name: 'nodeId',
118
+ wire: 'nodeId',
119
+ source: 'json',
120
+ codec: {
121
+ mode: 'strict',
122
+ typeSymbol: '@robiteame/dsh-pi-agent-session-tree#sessionTree/fork:nodeId',
123
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_parameter_1$schema,
124
+ },
125
+ },
126
+ {
127
+ name: 'branch',
128
+ wire: 'branch',
129
+ source: 'json',
130
+ codec: {
131
+ mode: 'strict',
132
+ typeSymbol: '@robiteame/dsh-pi-agent-session-tree#sessionTree/fork:branch',
133
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_parameter_2$schema,
134
+ },
135
+ },
136
+ ],
137
+ result: {
138
+ mode: 'strict',
139
+ typeSymbol: '@robiteame/dsh-pi-agent-session-tree#sessionTree/fork:result',
140
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_fork_result$schema,
141
+ },
142
+ sourceLocation: {"file":"packages/extensions/pi-agent-session-tree/src/index.ts","line":188,"column":3},
143
+ },
144
+ {
145
+ id: '@robiteame/dsh-pi-agent-session-tree#sessionTree/jump',
146
+ service: 'sessionTree',
147
+ namespace: 'sessionTree',
148
+ method: 'jump',
149
+ invocation: { kind: 'direct' },
150
+ scope: {
151
+ context: 'agent',
152
+ wire: 'agentId',
153
+ },
154
+ parameters: [
155
+ {
156
+ name: 'agent',
157
+ wire: 'agentId',
158
+ source: 'lookup',
159
+ lookup: 'agent',
160
+ codec: {
161
+ mode: 'strict',
162
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
163
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_jump_parameter_0$schema,
164
+ },
165
+ },
166
+ {
167
+ name: 'nodeId',
168
+ wire: 'nodeId',
169
+ source: 'json',
170
+ codec: {
171
+ mode: 'strict',
172
+ typeSymbol: '@robiteame/dsh-pi-agent-session-tree#sessionTree/jump:nodeId',
173
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_jump_parameter_1$schema,
174
+ },
175
+ },
176
+ ],
177
+ result: {
178
+ mode: 'strict',
179
+ typeSymbol: '@robiteame/dsh-pi-agent-session-tree/client#JumpView',
180
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_jump_result$schema,
181
+ },
182
+ sourceLocation: {"file":"packages/extensions/pi-agent-session-tree/src/index.ts","line":158,"column":3},
183
+ },
184
+ {
185
+ id: '@robiteame/dsh-pi-agent-session-tree#sessionTree/list',
186
+ service: 'sessionTree',
187
+ namespace: 'sessionTree',
188
+ method: 'list',
189
+ invocation: { kind: 'direct' },
190
+ scope: {
191
+ context: 'agent',
192
+ wire: 'agentId',
193
+ },
194
+ parameters: [
195
+ {
196
+ name: 'agent',
197
+ wire: 'agentId',
198
+ source: 'lookup',
199
+ lookup: 'agent',
200
+ codec: {
201
+ mode: 'strict',
202
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
203
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_list_parameter_0$schema,
204
+ },
205
+ },
206
+ ],
207
+ result: {
208
+ mode: 'strict',
209
+ typeSymbol: '@robiteame/dsh-pi-agent-session-tree/client#SessionTreeView',
210
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_list_result$schema,
211
+ },
212
+ sourceLocation: {"file":"packages/extensions/pi-agent-session-tree/src/index.ts","line":132,"column":3},
213
+ },
214
+ {
215
+ id: '@robiteame/dsh-pi-agent-session-tree#sessionTree/session',
216
+ service: 'sessionTree',
217
+ namespace: 'sessionTree',
218
+ method: 'session',
219
+ invocation: { kind: 'direct' },
220
+ scope: {
221
+ context: 'agent',
222
+ wire: 'agentId',
223
+ },
224
+ parameters: [
225
+ {
226
+ name: 'agent',
227
+ wire: 'agentId',
228
+ source: 'lookup',
229
+ lookup: 'agent',
230
+ codec: {
231
+ mode: 'strict',
232
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
233
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_session_parameter_0$schema,
234
+ },
235
+ },
236
+ ],
237
+ result: {
238
+ mode: 'strict',
239
+ typeSymbol: '@robiteame/dsh-pi-agent-session-tree/client#SessionTreeSessionInfo',
240
+ schema: _robiteame_dsh_pi_agent_session_tree_sessionTree_session_result$schema,
241
+ },
242
+ sourceLocation: {"file":"packages/extensions/pi-agent-session-tree/src/index.ts","line":211,"column":3},
243
+ },
244
+ ],
245
+ }
246
+
247
+ export default TYPERT_REMOTE
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Browser-safe type outlet: the pure payload vocabulary re-exported for the
3
+ * client half and consumed by the generated Remote declarations. Nothing here
4
+ * imports a host-only symbol; `dsh-session/types` is client-safe.
5
+ *
6
+ * @module @robiteame/dsh-pi-agent-session-tree/client
7
+ */
8
+ export type * from './types.ts';
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Session-tree domain service: the append-only multi-branch conversation
3
+ * tree, one per agent session, served to the browser through the generated
4
+ * `sessionTree` Remote namespace.
5
+ *
6
+ * Design notes
7
+ * - A process-wide store keeps every session's tree; the companion
8
+ * `@robiteame/dsh-tool-session-tree` plugin shares the same store, so
9
+ * anything the model appends is immediately visible to the browser panel
10
+ * and vice versa.
11
+ * - Harness Session events are the durable source of truth; the process-wide
12
+ * store is an incrementally synchronized projection, while explicit snapshots
13
+ * remain available for export and full-tree restore.
14
+ * - Every operation answers `{ok, value}|{ok:false,error}` from the domain
15
+ * layer; the Remote boundary adds its own transport envelope.
16
+ *
17
+ * @module @robiteame/dsh-pi-agent-session-tree
18
+ */
19
+ import type { Context } from '@deepseek-ai/cordis';
20
+ import type { Agent } from '@deepseek-ai/dsh-agent';
21
+ import type { Session } from '@deepseek-ai/dsh-session';
22
+ import type { SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session/types';
23
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
24
+ import { SessionTree } from './session-tree.ts';
25
+ import type { JumpView, SessionTreeSessionInfo, SessionTreeView } from './types.ts';
26
+ export { SessionTree, SessionTreeStore, sessionTreeStore } from './session-tree.ts';
27
+ export type * from './types.ts';
28
+ export { attachToolResult, sessionEventsToTreeNodes, toolResultOf, type ProjectedToolResult } from './session-event-adapter.ts';
29
+ export { getSessionTreeSidecar, persistSessionTree, setSessionTreeSidecar, SessionTreeSidecar } from './session-tree-sidecar.ts';
30
+ export { isSessionTreeRestoreEvent, sessionTreeMarkerOf, type SessionTreeRestoreMarker } from './session-tree-marker.ts';
31
+ declare module '@deepseek-ai/cordis' {
32
+ interface Context {
33
+ sessionTree: SessionTreeService;
34
+ }
35
+ }
36
+ /** Selected-message-surface API added by the repository's optional dev/session-branch-surface.patch. */
37
+ interface SelectedMessageSurfaceSession {
38
+ selectMessageSurface(nodes: readonly number[] | null): void;
39
+ messageSurfaceNodes(): readonly number[];
40
+ }
41
+ /**
42
+ * Whether a live Session exposes the selected-message-surface API shipped by
43
+ * this repository's `dev/session-branch-surface.patch`. A stock DeepSeek-Harness
44
+ * install (the target of `dsh plugin add`) has no such API: `deriveMessages()`
45
+ * always walks the canonical surface, and unknown `session-tree/*` events
46
+ * cannot be marked `ignorable` through the public append API, so a profile
47
+ * cannot persist them. The tree remains fully browsable and branchable inside
48
+ * the process store in that mode; only the in-place model-surface switch is
49
+ * unavailable.
50
+ */
51
+ export declare function supportsSelectedMessageSurface(session: Session): session is Session & SelectedMessageSurfaceSession;
52
+ /** How cursor movements reach the model-visible Session surface on this build. */
53
+ export type SessionTreeSurfaceMode = 'native' | 'stock' | 'projection';
54
+ /**
55
+ * The active surface mode for one session:
56
+ * - `native` — `dev/session-branch-surface.patch` (or an upstream merge)
57
+ * provides `Session.selectMessageSurface()`; jump/fork switch the model
58
+ * history directly.
59
+ * - `stock` — official Harness: the plugin emulates the switch with an official
60
+ * empty `replace` surface event plus the durable sidecar.
61
+ * - `projection` — the live surface is not writable in this revision;
62
+ * navigation updates the tree projection and panel only.
63
+ */
64
+ export declare function sessionTreeSurfaceMode(session: Session): SessionTreeSurfaceMode;
65
+ /**
66
+ * Whether the running Harness recognizes the durable `session-tree/*` event
67
+ * vocabulary. The event names are registered in the session known-event-types
68
+ * table by `dev/session-branch-surface.patch`; on stock packages an appended
69
+ * unknown event would make a resumed persisted log unreadable, so callers must
70
+ * skip those appends.
71
+ */
72
+ export declare function supportsDurableSessionTreeEvents(session: Session): boolean;
73
+ /** Durable SessionTree event types appended to the owning Session log. */
74
+ export type SessionTreeEventType = 'session-tree/node' | 'session-tree/cursor' | 'session-tree/branch' | 'session-tree/selection' | 'session-tree/snapshot';
75
+ /**
76
+ * Append one durable SessionTree marker when the runtime supports it, and
77
+ * return undefined on stock Harness so callers can skip sequence tracking.
78
+ */
79
+ export declare function appendSessionTreeEvent(session: Session, type: SessionTreeEventType, data: SessionEventMap[SessionTreeEventType]): SessionEvent<SessionTreeEventType> | undefined;
80
+ /** Materialize and incrementally synchronize native Harness history. */
81
+ export declare function syncSessionTree(agent: Agent): SessionTree;
82
+ /** Apply the selected tree path to Harness' actual model-visible Session surface. */
83
+ export declare function applyTreeCursorToSession(agent: Agent, tree: SessionTree): void;
84
+ /** Remote-only service backing the browser tree panel. */
85
+ export declare class SessionTreeService extends TypertRemoteService {
86
+ /**
87
+ * Register the service under `sessionTree`.
88
+ * @param ctx - owning Cordis Context.
89
+ */
90
+ constructor(ctx: Context);
91
+ /**
92
+ * Read the current tree view (nodes, branches, cursor) for one agent.
93
+ * The tree is created on first read, so an empty panel is valid.
94
+ * @param agent - owning live agent.
95
+ * @returns the complete view for the panel.
96
+ */
97
+ list(agent: Agent): SessionTreeView;
98
+ /** Synchronize native history, falling back to the last committed tree when replay fails. */
99
+ private synced;
100
+ /**
101
+ * Move the SessionTree cursor to an existing node and return its root-to-node
102
+ * path through the context operation. This also selects the same path on
103
+ * Harness' model-visible Session surface, so the next turn genuinely branches
104
+ * from this leaf instead of merely changing the browser projection.
105
+ * @param agent - owning live agent.
106
+ * @param nodeId - target node, or null to reset before the first node.
107
+ * @returns the new cursor and reconstructed messages.
108
+ * @throws Error when the node does not exist (settles as the standard error envelope).
109
+ */
110
+ jump(agent: Agent, nodeId: string | null): JumpView;
111
+ /**
112
+ * Position a named branch at a historical node for the next append.
113
+ * @param agent - owning live agent.
114
+ * @param nodeId - historical node to branch from.
115
+ * @param branch - non-empty branch label.
116
+ * @returns the parked cursor, branch label, and direct-child fork count.
117
+ */
118
+ fork(agent: Agent, nodeId: string, branch: string): {
119
+ cursor: string;
120
+ branch: string;
121
+ forkCount: number;
122
+ };
123
+ /**
124
+ * Read compact status metadata for the current session tree.
125
+ * @param agent - owning live agent.
126
+ * @returns current tree counts, cursor, branches, and usage metadata.
127
+ */
128
+ session(agent: Agent): SessionTreeSessionInfo;
129
+ }
130
+ export default SessionTreeService;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Package-owned invariant companion for `@robiteame/dsh-pi-agent-session-tree`.
3
+ * @module @robiteame/dsh-pi-agent-session-tree/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "pi-agent-session-tree-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pure projection between Harness SessionEvent logs and SessionTree nodes.
3
+ * This module does not subscribe to a live Session or persistence service: the
4
+ * host integration can feed it immutable `session.events` snapshots, while
5
+ * preserving the Session log as the durable source of truth.
6
+ */
7
+ import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
8
+ import type { TreeNode } from './types.ts';
9
+ /**
10
+ * Compact result payload of one `tool/result` event: the call correlation,
11
+ * the flattened result text, and the failure identity when the call errored.
12
+ */
13
+ export interface ProjectedToolResult {
14
+ callId: string;
15
+ text: string;
16
+ isError: boolean;
17
+ error?: string;
18
+ }
19
+ /** Extract the call correlation and outcome of one `tool/result` event. */
20
+ export declare function toolResultOf(event: SessionEvent): ProjectedToolResult | undefined;
21
+ /**
22
+ * Fold one projected tool result into its tool-call node so the call and its
23
+ * result read as a single tree entry (matching the model-visible pair). The
24
+ * function is pure and idempotent: an already-merged node is returned as-is.
25
+ */
26
+ export declare function attachToolResult(node: TreeNode, result: ProjectedToolResult, resultEventSeq: number | undefined): TreeNode;
27
+ /** Project the message-producing Harness events into an append-only tree. */
28
+ export declare function sessionEventsToTreeNodes(events: readonly SessionEvent[], initialParentId?: string | null): TreeNode[];
@@ -0,0 +1,13 @@
1
+ /** Marker carried by official surface events written by stock-mode tree jumps. */
2
+ import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
3
+ export interface SessionTreeRestoreMarker {
4
+ readonly kind: 'cursor';
5
+ readonly nodeId: string | null;
6
+ }
7
+ /**
8
+ * Read the plugin marker from an event's JSON data. Native Harness events
9
+ * never carry this key, so its presence is unambiguous.
10
+ */
11
+ export declare function sessionTreeMarkerOf(event: SessionEvent): SessionTreeRestoreMarker | undefined;
12
+ /** Whether one event is a synthetic cursor rewrite rather than conversation content. */
13
+ export declare function isSessionTreeRestoreEvent(event: SessionEvent): boolean;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Plugin-owned durable sidecar for SessionTree projections.
3
+ *
4
+ * The stock DeepSeek-Harness session log does not recognize `session-tree/*`
5
+ * events, so branch names, cursor, selection, and explicit snapshot state
6
+ * cannot live inside `session.jsonl.zstd` without patching the harness. This
7
+ * module keeps one small JSON artifact per session under the DSH home instead
8
+ * and replays only native events after the stored watermark on resume.
9
+ */
10
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
11
+ import { SessionTree } from './session-tree.ts';
12
+ /** Synchronous sidecar store used by host-only session-tree plugins. */
13
+ export declare class SessionTreeSidecar {
14
+ readonly root: string;
15
+ constructor(root?: string);
16
+ /** Read and validate the latest snapshot for one session. */
17
+ load(sessionId: SessionId): SessionTree | undefined;
18
+ /**
19
+ * Atomically persist one tree. I/O errors are deliberately contained:
20
+ * the native Session log remains the source of truth and can rebuild a
21
+ * linear projection even when the sidecar directory is unavailable.
22
+ */
23
+ save(sessionId: SessionId, tree: SessionTree): boolean;
24
+ }
25
+ export declare function getSessionTreeSidecar(): SessionTreeSidecar;
26
+ /** Replace the active sidecar, primarily for isolated tests. */
27
+ export declare function setSessionTreeSidecar(sidecar: SessionTreeSidecar): void;
28
+ /** Persist one tree without coupling callers to the active store selection. */
29
+ export declare function persistSessionTree(tree: SessionTree): boolean;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Append-only multi-branch session tree. One tree owns one sessionId; the
3
+ * store holds many trees side by side. All mutations are additions: a node
4
+ * is never edited or removed after creation, and navigation only moves the
5
+ * cursor, so every history branch survives (PI-Agent SessionTree semantics:
6
+ * append-only entries, leaf-pointer branching, root-to-leaf message paths).
7
+ *
8
+ * @module @robiteame/dsh-pi-agent-session-tree/session-tree
9
+ */
10
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
11
+ import type { BranchView, JsonValue, JumpView, ContentPart, LlmMessage, SessionTreeSessionInfo, SessionTreeSnapshot, SessionTreeView, TreeEntryType, TreeNode, SessionTreeLogEntry, TreeResult } from './types.ts';
12
+ /** One stored node plus the internal cursor bookkeeping. */
13
+ export declare class SessionTree {
14
+ readonly sessionId: SessionId;
15
+ private readonly nodesById;
16
+ private cursorId;
17
+ private selectedNodeId;
18
+ private activeBranchName;
19
+ private readonly branchHeads;
20
+ private syncedSessionEventSeq;
21
+ /**
22
+ * Create an empty tree, or restore one from a versioned snapshot.
23
+ * @param sessionId - owning session identity.
24
+ * @param snapshot - optional durable snapshot to restore.
25
+ * @throws Error when the snapshot is malformed (version, ownership, cursor).
26
+ */
27
+ constructor(sessionId: SessionId, snapshot?: unknown);
28
+ /** Mint a node id guaranteed absent from this tree (collision-safe). */
29
+ private mintNodeId;
30
+ /** @returns the current cursor node id, or null before the first append. */
31
+ get cursor(): string | null;
32
+ /** Explicit node bound to context-aware UI commands. */
33
+ get selectedNode(): string | null;
34
+ /** Bind /fork and /clone to an existing node without changing cursor semantics. */
35
+ select(nodeId: string): TreeResult<{
36
+ nodeId: string;
37
+ }>;
38
+ /** @returns the branch label the next append joins. */
39
+ get activeBranch(): string;
40
+ /**
41
+ * Append one new node as a child of the cursor. The cursor's parent chain
42
+ * is never touched; the new node joins the active branch unless one is
43
+ * named. Historical nodes stay byte-identical.
44
+ * @param message - standard LLM message to carry.
45
+ * @param options - optional branch override, summary, and JSON extras.
46
+ * @returns the created node, or a standard error for invalid input.
47
+ */
48
+ /** Capture mutable projection state so an external durable write can commit atomically. */
49
+ checkpoint(): SessionTreeSnapshot;
50
+ /** Roll back to a previously captured checkpoint after a durable append fails. */
51
+ rollback(snapshot: SessionTreeSnapshot): TreeResult<void>;
52
+ append(message: LlmMessage, options?: {
53
+ branch?: string;
54
+ summary?: string;
55
+ content?: readonly ContentPart[];
56
+ model?: string;
57
+ usage?: Record<string, JsonValue>;
58
+ cost?: number;
59
+ error?: string;
60
+ metadata?: Record<string, JsonValue>;
61
+ }): TreeResult<TreeNode>;
62
+ /**
63
+ * Move the cursor to an existing node (root-to-node path replay). Old
64
+ * branches remain intact; the next append forks from the target.
65
+ * @param target - target node id, or null to reset before any node.
66
+ * @returns the new cursor and the reconstructed root-to-cursor messages.
67
+ */
68
+ jump(target: string | null): TreeResult<JumpView>;
69
+ /**
70
+ * Fork the active cursor from a historical node. This is the Pi `/fork`
71
+ * primitive: old nodes remain intact and the next tree append becomes a child.
72
+ * The owning service selects the matching Harness model surface path.
73
+ */
74
+ fork(target: string, branch?: string): TreeResult<{
75
+ cursor: string;
76
+ branch: string;
77
+ forkCount: number;
78
+ }>;
79
+ /**
80
+ * Set the branch label the next append joins and park the cursor at the
81
+ * chosen node, without appending anything.
82
+ * @param target - node to branch from (must exist).
83
+ * @param branch - new branch label (non-empty).
84
+ * @returns the new cursor and branch.
85
+ */
86
+ branch(target: string, branch: string): TreeResult<{
87
+ cursor: string;
88
+ branch: string;
89
+ }>;
90
+ /**
91
+ * Branch with a summary of the abandoned path: park the cursor at `target`
92
+ * (like {@link branch}), then append a summary node whose parent is the
93
+ * target. The abandoned branch's nodes stay untouched.
94
+ * @param target - node to branch from.
95
+ * @param summary - summary text stored on the new node.
96
+ * @returns the created summary node.
97
+ */
98
+ branchWithSummary(target: string, summary: string): TreeResult<TreeNode>;
99
+ /**
100
+ * Reconstruct the standard LLM messages array for the path root→node
101
+ * (defaults to the cursor). System/user/assistant/tool messages keep their
102
+ * roles; branch-summary nodes carry no message and stay out of the array.
103
+ * @param from - target node id, or null for the empty path.
104
+ * @returns messages in conversation order (root first).
105
+ */
106
+ messages(from?: string | null): LlmMessage[];
107
+ /**
108
+ * @returns all nodes in creation order (defensive copies).
109
+ */
110
+ log(fromSeq?: number): SessionTreeLogEntry[];
111
+ replay(records: readonly SessionTreeLogEntry[]): TreeResult<{
112
+ applied: number;
113
+ }>;
114
+ list(): TreeNode[];
115
+ /**
116
+ * @returns named branch pointers and their reachable nodes. Branch identity
117
+ * comes from the session-level head map; node.branch is display metadata only.
118
+ */
119
+ branches(): BranchView[];
120
+ /** Return a compact Pi-style session status projection. */
121
+ info(): SessionTreeSessionInfo;
122
+ /** @returns the full read view served to the browser panel. */
123
+ view(): SessionTreeView;
124
+ /** @returns a versioned durable snapshot (defensive copy). */
125
+ snapshot(): SessionTreeSnapshot;
126
+ /** Return the current leaf's root-to-leaf node path. */
127
+ currentPath(from?: string | null): TreeNode[];
128
+ /** Return the greatest native Session event seq already projected into this tree. */
129
+ lastSessionEventSeq(): number;
130
+ /** Advance the native Session event watermark after a successful sync/write. */
131
+ markSessionEventSeq(seq: number): void;
132
+ /** Rewind the watermark when a persisted native tail is shorter than the sidecar expected. */
133
+ limitSessionEventSeq(seq: number): void;
134
+ /** @returns true when a node with this id exists. */
135
+ has(nodeId: string): boolean;
136
+ /**
137
+ * Find the dedicated tool-call node carrying this call id. Assistant
138
+ * message nodes may echo the same tool-call block in their content, but a
139
+ * tool result must never fold into them: only `type === 'tool_call'` nodes
140
+ * represent the interaction entry the panel renders.
141
+ */
142
+ findToolCallNode(callId: string): TreeNode | undefined;
143
+ /**
144
+ * Replace a projected tool-call node with its result-merged form, so one
145
+ * call and its result stay one tree entry even when the result arrives in a
146
+ * later sync batch than the call. The tree is a deterministic projection of
147
+ * the durable Session log, so this derived update never rewrites history in
148
+ * the log itself and replays identically from scratch.
149
+ */
150
+ attachToolResult(callId: string, merged: TreeNode): TreeResult<TreeNode>;
151
+ /** @returns direct child ids, used to expose the derived fork count. */
152
+ private directChildren;
153
+ private messagesFrom;
154
+ }
155
+ /** Keyed store: many independent trees coexist, one per session. */
156
+ export declare class SessionTreeStore {
157
+ private readonly trees;
158
+ /** Look up a tree without creating one. */
159
+ get(sessionId: SessionId): SessionTree | undefined;
160
+ /** Look up a tree, or return the standard SESSION_NOT_FOUND error. */
161
+ require(sessionId: SessionId): TreeResult<SessionTree>;
162
+ /** Create a new tree session. Existing ids are never silently reset. */
163
+ create(sessionId: SessionId): SessionTree;
164
+ /** Clone the active branch into an independent session with fresh node ids. */
165
+ clone(sourceSessionId: SessionId, targetSessionId: SessionId): TreeResult<{
166
+ sessionId: string;
167
+ }>;
168
+ /** Replace one session's tree from a previously validated candidate. */
169
+ replace(sessionId: SessionId, tree: SessionTree): void;
170
+ /** Replace one session's tree from a snapshot. */
171
+ load(snapshot: SessionTreeSnapshot): TreeResult<{
172
+ sessionId: string;
173
+ }>;
174
+ /** @returns all session ids with a live tree, in creation order. */
175
+ list(): string[];
176
+ }
177
+ export type { BranchView, ContentPart, JumpView, LlmMessage, SessionTreeLogEntry, SessionTreeSessionInfo, SessionTreeSnapshot, SessionTreeView, TreeEntryType, TreeNode, TreeResult };
178
+ /**
179
+ * Process-wide store shared by the domain service and the tool/command
180
+ * companion, so model appends and browser jumps read the same trees.
181
+ */
182
+ export declare const sessionTreeStore: SessionTreeStore;