refactor(surface): use nodeBySeq map for lookup in _replace, drop dead params
This commit is contained in:
@@ -66,7 +66,51 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|||||||
|
|
||||||
`SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`.
|
`SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`.
|
||||||
|
|
||||||
The five message-producing types (`SurfaceEventType` — `user/message`, `assistant/message`, `tool/result`, `context/message`, `steering/message`) additionally carry two optional surface fields: `surfaceOp` (how the event enters the derived surface linked list — `'append'` or a `{ op: 'replace', start, end }` shadow) and `sourceEventSeqs` (provenance). See the [session surface RFC](../rfc/implemented/architecture/2026-06-18-session-surface.md).
|
## Surface types
|
||||||
|
|
||||||
|
The five message-producing types (`SurfaceEventType` — `user/message`, `assistant/message`, `tool/result`, `context/message`, `steering/message`) carry surface metadata declaring how they join the derived surface linked list. See the [session surface RFC](../rfc/implemented/architecture/2026-06-18-session-surface.md).
|
||||||
|
|
||||||
|
### `SurfaceEventType` — the message-producing subset of event types
|
||||||
|
|
||||||
|
```ts type-equiv
|
||||||
|
export type SurfaceEventType =
|
||||||
|
| 'user/message'
|
||||||
|
| 'assistant/message'
|
||||||
|
| 'tool/result'
|
||||||
|
| 'context/message'
|
||||||
|
| 'steering/message'
|
||||||
|
```
|
||||||
|
|
||||||
|
### `SurfaceOp` — how an event entered the surface
|
||||||
|
|
||||||
|
```ts type-equiv
|
||||||
|
export type SurfaceOp =
|
||||||
|
| 'append'
|
||||||
|
| { op: 'replace'; start: number; end: number }
|
||||||
|
```
|
||||||
|
|
||||||
|
`'append'` is the normal tail-append path. `replace` shadows surface nodes from `start` through `end` inclusive (both must be valid surface node seqs; `start === end` replaces a single node) and inserts the new node in their place.
|
||||||
|
|
||||||
|
### `SurfaceIntent` — the parameter to `session.append()`
|
||||||
|
|
||||||
|
```ts type-equiv
|
||||||
|
export interface SurfaceIntent {
|
||||||
|
surfaceOp: SurfaceOp
|
||||||
|
sourceEventSeqs?: number[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Required for `SurfaceEventType` events — every message-producing event must declare how it joins the surface, the sole source of derived history. Non-surface types reject it at compile time.
|
||||||
|
|
||||||
|
### `SurfaceNode` — a node in the surface linked list
|
||||||
|
|
||||||
|
```ts type-equiv
|
||||||
|
export interface SurfaceNode {
|
||||||
|
seq: number
|
||||||
|
prev: number | null
|
||||||
|
next: number | null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Derived history: `deriveMessages()`
|
## Derived history: `deriveMessages()`
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export interface SurfaceNode {
|
|||||||
export class SurfaceManager {
|
export class SurfaceManager {
|
||||||
/** Surface nodes in linked-list order (head to tail). Empty until first access. */
|
/** Surface nodes in linked-list order (head to tail). Empty until first access. */
|
||||||
private _nodes: SurfaceNode[] = []
|
private _nodes: SurfaceNode[] = []
|
||||||
/** Map from event seq → node for O(1) lookup during replacements. */
|
/** Map from event seq → node. */
|
||||||
private _nodeBySeq = new Map<number, SurfaceNode>()
|
private _nodeBySeq = new Map<number, SurfaceNode>()
|
||||||
/** The last processed seq. -1 forces a full rebuild on first access. */
|
/** The last processed seq. -1 forces a full rebuild on first access. */
|
||||||
private _lastProcessedSeq = -1
|
private _lastProcessedSeq = -1
|
||||||
@@ -100,7 +100,7 @@ export class SurfaceManager {
|
|||||||
this._nodes.push(node)
|
this._nodes.push(node)
|
||||||
this._nodeBySeq.set(event.seq, node)
|
this._nodeBySeq.set(event.seq, node)
|
||||||
} else {
|
} else {
|
||||||
this._replace(this._nodes, this._nodeBySeq, event.seq, event.surfaceOp)
|
this._replace(event.seq, event.surfaceOp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._lastProcessedSeq = this.log.length - 1
|
this._lastProcessedSeq = this.log.length - 1
|
||||||
@@ -108,31 +108,31 @@ export class SurfaceManager {
|
|||||||
|
|
||||||
/** Apply a replace operation to the in-progress surface. */
|
/** Apply a replace operation to the in-progress surface. */
|
||||||
private _replace(
|
private _replace(
|
||||||
nodes: SurfaceNode[],
|
|
||||||
nodeBySeq: Map<number, SurfaceNode>,
|
|
||||||
newSeq: number,
|
newSeq: number,
|
||||||
op: Extract<SurfaceOp, { op: 'replace' }>,
|
op: Extract<SurfaceOp, { op: 'replace' }>,
|
||||||
): void {
|
): void {
|
||||||
const startIdx = nodes.findIndex(n => n.seq === op.start)
|
const startNode = this._nodeBySeq.get(op.start)
|
||||||
if (startIdx === -1) {
|
if (!startNode) {
|
||||||
throw new Error(`surface replace: start seq ${op.start} not found in surface`)
|
throw new Error(`surface replace: start seq ${op.start} not found in surface`)
|
||||||
}
|
}
|
||||||
const endIdx = nodes.findIndex(n => n.seq === op.end)
|
const endNode = this._nodeBySeq.get(op.end)
|
||||||
if (endIdx === -1) {
|
if (!endNode) {
|
||||||
throw new Error(`surface replace: end seq ${op.end} not found in surface`)
|
throw new Error(`surface replace: end seq ${op.end} not found in surface`)
|
||||||
}
|
}
|
||||||
|
const startIdx = this._nodes.indexOf(startNode)
|
||||||
|
const endIdx = this._nodes.indexOf(endNode)
|
||||||
if (startIdx > endIdx) {
|
if (startIdx > endIdx) {
|
||||||
throw new Error(`surface replace: start seq ${op.start} (index ${startIdx}) is after end seq ${op.end} (index ${endIdx})`)
|
throw new Error(`surface replace: start seq ${op.start} (index ${startIdx}) is after end seq ${op.end} (index ${endIdx})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove shadowed nodes from `[startIdx, endIdx]` inclusive.
|
// Remove shadowed nodes from `[startIdx, endIdx]` inclusive.
|
||||||
const count = endIdx - startIdx + 1
|
const count = endIdx - startIdx + 1
|
||||||
const removed = nodes.splice(startIdx, count)
|
const removed = this._nodes.splice(startIdx, count)
|
||||||
for (const r of removed) nodeBySeq.delete(r.seq)
|
for (const r of removed) this._nodeBySeq.delete(r.seq)
|
||||||
|
|
||||||
// Insert the new node where the removed range was.
|
// Insert the new node where the removed range was.
|
||||||
const prevNode = startIdx > 0 ? nodes[startIdx - 1] : undefined
|
const prevNode = startIdx > 0 ? this._nodes[startIdx - 1] : undefined
|
||||||
const nextNode = startIdx < nodes.length ? nodes[startIdx] : undefined
|
const nextNode = startIdx < this._nodes.length ? this._nodes[startIdx] : undefined
|
||||||
|
|
||||||
const newNode: SurfaceNode = {
|
const newNode: SurfaceNode = {
|
||||||
seq: newSeq,
|
seq: newSeq,
|
||||||
@@ -141,7 +141,7 @@ export class SurfaceManager {
|
|||||||
}
|
}
|
||||||
if (prevNode) prevNode.next = newSeq
|
if (prevNode) prevNode.next = newSeq
|
||||||
if (nextNode) nextNode.prev = newSeq
|
if (nextNode) nextNode.prev = newSeq
|
||||||
nodes.splice(startIdx, 0, newNode)
|
this._nodes.splice(startIdx, 0, newNode)
|
||||||
nodeBySeq.set(newSeq, newNode)
|
this._nodeBySeq.set(newSeq, newNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" },
|
{ "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" },
|
||||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "TurnTriggerMap", "source": "packages/core/session/src/types.ts" },
|
{ "doc": "docs/core-data-structures/session.md", "symbol": "TurnTriggerMap", "source": "packages/core/session/src/types.ts" },
|
||||||
{ "doc": "docs/core-data-structures/session.md", "symbol": "TurnEndReasonMap", "source": "packages/core/session/src/types.ts" },
|
{ "doc": "docs/core-data-structures/session.md", "symbol": "TurnEndReasonMap", "source": "packages/core/session/src/types.ts" },
|
||||||
|
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceEventType", "source": "packages/core/session/src/types.ts" },
|
||||||
|
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceOp", "source": "packages/core/session/src/types.ts" },
|
||||||
|
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceIntent", "source": "packages/core/session/src/types.ts" },
|
||||||
|
{ "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceNode", "source": "packages/core/session/src/surface.ts" },
|
||||||
|
|
||||||
{ "doc": "docs/core-data-structures/persistence.md", "symbol": "SessionHeader", "source": "packages/core/session/src/types.ts" },
|
{ "doc": "docs/core-data-structures/persistence.md", "symbol": "SessionHeader", "source": "packages/core/session/src/types.ts" },
|
||||||
{ "doc": "docs/core-data-structures/persistence.md", "symbol": "CreateSessionOptions", "source": "packages/core/session/src/types.ts" },
|
{ "doc": "docs/core-data-structures/persistence.md", "symbol": "CreateSessionOptions", "source": "packages/core/session/src/types.ts" },
|
||||||
|
|||||||
Reference in New Issue
Block a user