Inline session fork parameters
This commit is contained in:
@@ -143,6 +143,6 @@ New behavior should attach to a documented seam; changing the shipped loop requi
|
|||||||
| Intercept prompts, requests, tool use, or continuation | listen on the relevant `agent/*` or `tools/*` waterfall |
|
| Intercept prompts, requests, tool use, or continuation | listen on the relevant `agent/*` or `tools/*` waterfall |
|
||||||
| Add UI or editor integration | drive `ctx.agents` and render from `session/event` |
|
| Add UI or editor integration | drive `ctx.agents` and render from `session/event` |
|
||||||
| Add durable session state | add a `SessionEventMap` member and render/replay from the log |
|
| Add durable session state | add a `SessionEventMap` member and render/replay from the log |
|
||||||
| Fork a live session | use `ctx.sessions.fork({ source, boundary?, childSessionId? })` |
|
| Fork a live session | use `ctx.sessions.fork(source, boundary?, childSessionId?)` |
|
||||||
|
|
||||||
The [extension cookbook](cookbook/extension-cookbook.md) carries plugin skeletons and the feature-to-seam map; step-by-step guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md).
|
The [extension cookbook](cookbook/extension-cookbook.md) carries plugin skeletons and the feature-to-seam map; step-by-step guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md).
|
||||||
|
|||||||
@@ -161,10 +161,10 @@ enter(session: Session): () => void
|
|||||||
announce(session: Session): void
|
announce(session: Session): void
|
||||||
get(id: SessionId): Session | undefined
|
get(id: SessionId): Session | undefined
|
||||||
list(): Session[]
|
list(): Session[]
|
||||||
fork(options: ForkSessionOptions): Session
|
fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session
|
||||||
```
|
```
|
||||||
|
|
||||||
Source: [`packages/core/session/src/index.ts:402`](../../packages/core/session/src/index.ts)
|
Source: [`packages/core/session/src/index.ts:389`](../../packages/core/session/src/index.ts)
|
||||||
|
|
||||||
## `ctx.subagents` — `SubagentService`
|
## `ctx.subagents` — `SubagentService`
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ Everything else (`turn/*`, `step/*`) is structural and does not project into a m
|
|||||||
|
|
||||||
`ctx.sessions.create(id, { seed, meta })` is the low-level replay/fork primitive. For ordinary live-session forks, `SessionStore` exposes one policy API:
|
`ctx.sessions.create(id, { seed, meta })` is the low-level replay/fork primitive. For ordinary live-session forks, `SessionStore` exposes one policy API:
|
||||||
|
|
||||||
- `fork({ source, boundary?, childSessionId? })` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `boundary` seq (default: current last event), requires the boundary event to be `turn/end`, then creates a live child session with deep-cloned seed events plus child metadata (`parentSession`, `seedLength`, and inherited `cwd`).
|
- `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `boundary` seq (default: current last event), requires the boundary event to be `turn/end`, then creates a live child session with deep-cloned seed events plus child metadata (`parentSession`, `seedLength`, and inherited `cwd`).
|
||||||
|
|
||||||
An explicit `boundary` lets callers fork from a previous completed turn even if the source has newer events or an open current turn. The API rejects non-`turn/end` boundaries instead of clipping silently. Broader turn-enclosure sanity stays in the existing `dsh-invariants` plugin and persistence repair path rather than being duplicated in `fork()`. `dsh-subagent-fork` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit.
|
An explicit `boundary` lets callers fork from a previous completed turn even if the source has newer events or an open current turn. The API rejects non-`turn/end` boundaries instead of clipping silently. Broader turn-enclosure sanity stays in the existing `dsh-invariants` plugin and persistence repair path rather than being duplicated in `fork()`. `dsh-subagent-fork` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit.
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ The store exposes one operation:
|
|||||||
```ts ignore-check
|
```ts ignore-check
|
||||||
type SessionForkSource = Session | SessionId
|
type SessionForkSource = Session | SessionId
|
||||||
|
|
||||||
interface ForkSessionOptions {
|
|
||||||
source: SessionForkSource
|
|
||||||
boundary?: number
|
|
||||||
childSessionId?: SessionId
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionStore extends Service {
|
class SessionStore extends Service {
|
||||||
fork(options: ForkSessionOptions): Session
|
fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Creates and holds event-sourced `Session` instances. Persistence is intentionall
|
|||||||
### Public API
|
### Public API
|
||||||
|
|
||||||
- `ctx.sessions.create(id?: SessionId, options?: { seed?: SessionEvent[]; meta?: { cwd?: string; parentSession?: SessionId; createdAt?: number; seedLength?: number } }): Session` — Create a session. `options.seed` replays/forks an existing event log; `options.meta` attaches creation metadata (validated absolute `cwd`, `parentSession` lineage, seed boundary) as the immutable `SessionHeader`. The store fills `version`/`id` and defaults `createdAt` to now; a caller reconstructing a persisted session passes the original `createdAt` and persisted `seedLength` to preserve them. Disposed with the calling fiber.
|
- `ctx.sessions.create(id?: SessionId, options?: { seed?: SessionEvent[]; meta?: { cwd?: string; parentSession?: SessionId; createdAt?: number; seedLength?: number } }): Session` — Create a session. `options.seed` replays/forks an existing event log; `options.meta` attaches creation metadata (validated absolute `cwd`, `parentSession` lineage, seed boundary) as the immutable `SessionHeader`. The store fills `version`/`id` and defaults `createdAt` to now; a caller reconstructing a persisted session passes the original `createdAt` and persisted `seedLength` to preserve them. Disposed with the calling fiber.
|
||||||
- `ctx.sessions.fork({ source, boundary?, childSessionId? }): Session` — Resolve a live session object or id, select a seed through the inclusive `boundary` event seq (default: current last event), require that boundary to be `turn/end`, and create a live child session with lineage metadata.
|
- `ctx.sessions.fork(source, boundary?, childSessionId?): Session` — Resolve a live session object or id, select a seed through the inclusive `boundary` event seq (default: current last event), require that boundary to be `turn/end`, and create a live child session with lineage metadata.
|
||||||
- `ctx.sessions.get(id: SessionId): Session | undefined`
|
- `ctx.sessions.get(id: SessionId): Session | undefined`
|
||||||
- `ctx.sessions.list(): Session[]`
|
- `ctx.sessions.list(): Session[]`
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ Every `SessionEvent` carries two optional top-level fields (structural metadata)
|
|||||||
### Extension points
|
### Extension points
|
||||||
|
|
||||||
- Persistence plugins: subscribe to `session/event` (write-behind) and drain on `session/flush` (awaited) and fiber dispose. A durable backend reads the log and reloads it into a live session; the metadata seam (`SessionHeader`, `session.header`) is what such a backend stores beside the log.
|
- Persistence plugins: subscribe to `session/event` (write-behind) and drain on `session/flush` (awaited) and fiber dispose. A durable backend reads the log and reloads it into a live session; the metadata seam (`SessionHeader`, `session.header`) is what such a backend stores beside the log.
|
||||||
- Replay/fork: `ctx.sessions.create(id, { seed })` seeds a new session with an existing event log. The surface rebuilds deterministically from `surfaceOp` markers in the seeded events. The seed is validated to the SAME always-on invariants `append` enforces — contiguous seqs, JSON-serializable data, and required `surfaceOp` markers on surface-eligible events — so marker-less message events are rejected at construction rather than silently vanishing from `deriveMessages()`. Broader turn-enclosure checks stay in `dsh-invariants` and persistence repair. Ordinary live-session forks use `ctx.sessions.fork({ source, boundary?, childSessionId? })`, where `boundary` is the inclusive source event seq to fork through.
|
- Replay/fork: `ctx.sessions.create(id, { seed })` seeds a new session with an existing event log. The surface rebuilds deterministically from `surfaceOp` markers in the seeded events. The seed is validated to the SAME always-on invariants `append` enforces — contiguous seqs, JSON-serializable data, and required `surfaceOp` markers on surface-eligible events — so marker-less message events are rejected at construction rather than silently vanishing from `deriveMessages()`. Broader turn-enclosure checks stay in `dsh-invariants` and persistence repair. Ordinary live-session forks use `ctx.sessions.fork(source, boundary?, childSessionId?)`, where `boundary` is the inclusive source event seq to fork through.
|
||||||
- Compaction: the `dsh-compact-basic` plugin appends a `user/message` with `surfaceOp: { op: 'replace', start, end }` to shadow old surface nodes behind a summary checkpoint.
|
- Compaction: the `dsh-compact-basic` plugin appends a `user/message` with `surfaceOp: { op: 'replace', start, end }` to shadow old surface nodes behind a summary checkpoint.
|
||||||
|
|
||||||
### What is NOT here (TODO)
|
### What is NOT here (TODO)
|
||||||
|
|||||||
@@ -365,19 +365,6 @@ export class Session {
|
|||||||
/** A fork source: either the live session object or its live store id. */
|
/** A fork source: either the live session object or its live store id. */
|
||||||
export type SessionForkSource = Session | SessionId
|
export type SessionForkSource = Session | SessionId
|
||||||
|
|
||||||
/** Inputs for live session forking. */
|
|
||||||
export interface ForkSessionOptions {
|
|
||||||
/** Live source session object or id. */
|
|
||||||
source: SessionForkSource
|
|
||||||
/**
|
|
||||||
* Inclusive source event seq to fork through. Omitted means the source's
|
|
||||||
* current last event; omitted on an empty source forks an empty child.
|
|
||||||
*/
|
|
||||||
boundary?: number
|
|
||||||
/** Optional child session id; omitted delegates to SessionStore's id policy. */
|
|
||||||
childSessionId?: SessionId
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SessionForkErrorCode =
|
export type SessionForkErrorCode =
|
||||||
| 'SESSION_NOT_FOUND'
|
| 'SESSION_NOT_FOUND'
|
||||||
| 'SESSION_NOT_LIVE'
|
| 'SESSION_NOT_LIVE'
|
||||||
@@ -533,20 +520,25 @@ export class SessionStore extends Service {
|
|||||||
* `boundary` is an inclusive source event seq; omitted means the source's
|
* `boundary` is an inclusive source event seq; omitted means the source's
|
||||||
* current last event. A non-empty selected slice must end at `turn/end`.
|
* current last event. A non-empty selected slice must end at `turn/end`.
|
||||||
*
|
*
|
||||||
* @param options Source, optional boundary, and optional child id for the fork.
|
* @param source - Live source session object or id.
|
||||||
|
* @param boundary - Inclusive source event seq to fork through; omitted means
|
||||||
|
* the source's current last event, and omitted on an empty source forks an
|
||||||
|
* empty child.
|
||||||
|
* @param childSessionId - Optional child session id; omitted delegates to
|
||||||
|
* `SessionStore`'s id policy.
|
||||||
* @returns The created live child session.
|
* @returns The created live child session.
|
||||||
*/
|
*/
|
||||||
fork(options: ForkSessionOptions): Session {
|
fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session {
|
||||||
if (options.childSessionId !== undefined && this.get(options.childSessionId) !== undefined) {
|
if (childSessionId !== undefined && this.get(childSessionId) !== undefined) {
|
||||||
throw new SessionForkError(`session "${options.childSessionId}" already exists`, 'SESSION_ALREADY_EXISTS')
|
throw new SessionForkError(`session "${childSessionId}" already exists`, 'SESSION_ALREADY_EXISTS')
|
||||||
}
|
}
|
||||||
const source = this._resolveForkSource(options.source)
|
const liveSource = this._resolveForkSource(source)
|
||||||
const seed = this._forkSeed(source, options.boundary)
|
const seed = this._forkSeed(liveSource, boundary)
|
||||||
return this.create(options.childSessionId, {
|
return this.create(childSessionId, {
|
||||||
seed,
|
seed,
|
||||||
meta: {
|
meta: {
|
||||||
...source.header.cwd !== undefined ? { cwd: source.header.cwd } : {},
|
...liveSource.header.cwd !== undefined ? { cwd: liveSource.header.cwd } : {},
|
||||||
parentSession: source.id,
|
parentSession: liveSource.id,
|
||||||
seedLength: seed.length,
|
seedLength: seed.length,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ describe('SessionStore.fork', () => {
|
|||||||
const { ctx, sessions } = await setup()
|
const { ctx, sessions } = await setup()
|
||||||
const source = ctx.sessions.create(SessionId('empty-parent'), { meta: { cwd: '/workspace' } })
|
const source = ctx.sessions.create(SessionId('empty-parent'), { meta: { cwd: '/workspace' } })
|
||||||
|
|
||||||
const child = sessions.fork({ source, childSessionId: SessionId('empty-child') })
|
const child = sessions.fork(source, undefined, SessionId('empty-child'))
|
||||||
|
|
||||||
expect(child.events).toEqual([])
|
expect(child.events).toEqual([])
|
||||||
expect(child.header).toMatchObject({
|
expect(child.header).toMatchObject({
|
||||||
@@ -65,7 +65,7 @@ describe('SessionStore.fork', () => {
|
|||||||
const source = ctx.sessions.create(SessionId('parent'), { meta: { cwd: '/workspace' } })
|
const source = ctx.sessions.create(SessionId('parent'), { meta: { cwd: '/workspace' } })
|
||||||
appendClosedTurn(source, 1, 'hello')
|
appendClosedTurn(source, 1, 'hello')
|
||||||
|
|
||||||
const child = sessions.fork({ source: SessionId('parent'), childSessionId: SessionId('child') })
|
const child = sessions.fork(SessionId('parent'), undefined, SessionId('child'))
|
||||||
|
|
||||||
expect(child.events).toEqual(source.events)
|
expect(child.events).toEqual(source.events)
|
||||||
expect(child.events).not.toBe(source.events)
|
expect(child.events).not.toBe(source.events)
|
||||||
@@ -88,11 +88,7 @@ describe('SessionStore.fork', () => {
|
|||||||
appendClosedTurn(source, 2, 'second')
|
appendClosedTurn(source, 2, 'second')
|
||||||
appendOpenTurn(source, 3)
|
appendOpenTurn(source, 3)
|
||||||
|
|
||||||
const child = sessions.fork({
|
const child = sessions.fork(source, firstBoundary, SessionId('child-from-first'))
|
||||||
source,
|
|
||||||
boundary: firstBoundary,
|
|
||||||
childSessionId: SessionId('child-from-first'),
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(child.events).toEqual(source.events.slice(0, firstBoundary + 1))
|
expect(child.events).toEqual(source.events.slice(0, firstBoundary + 1))
|
||||||
expect(child.header.seedLength).toBe(firstBoundary + 1)
|
expect(child.header.seedLength).toBe(firstBoundary + 1)
|
||||||
@@ -114,11 +110,7 @@ describe('SessionStore.fork', () => {
|
|||||||
const source = ctx.sessions.create(SessionId(`parent-${reason.kind}`))
|
const source = ctx.sessions.create(SessionId(`parent-${reason.kind}`))
|
||||||
appendClosedTurn(source, 1, reason.kind, reason)
|
appendClosedTurn(source, 1, reason.kind, reason)
|
||||||
|
|
||||||
const child = sessions.fork({
|
const child = sessions.fork(source, lastSeq(source), SessionId(`child-${reason.kind}`))
|
||||||
source,
|
|
||||||
boundary: lastSeq(source),
|
|
||||||
childSessionId: SessionId(`child-${reason.kind}`),
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(child.events.at(-1)?.type).toBe('turn/end')
|
expect(child.events.at(-1)?.type).toBe('turn/end')
|
||||||
expect(child.header.seedLength).toBe(source.events.length)
|
expect(child.header.seedLength).toBe(source.events.length)
|
||||||
@@ -128,19 +120,19 @@ describe('SessionStore.fork', () => {
|
|||||||
it('rejects invalid boundaries before creating a child', async () => {
|
it('rejects invalid boundaries before creating a child', async () => {
|
||||||
const { ctx, sessions } = await setup()
|
const { ctx, sessions } = await setup()
|
||||||
const empty = ctx.sessions.create(SessionId('empty'))
|
const empty = ctx.sessions.create(SessionId('empty'))
|
||||||
expect(() => sessions.fork({ source: empty, boundary: 0, childSessionId: SessionId('empty-child') }))
|
expect(() => sessions.fork(empty, 0, SessionId('empty-child')))
|
||||||
.toThrow(new SessionForkError('fork boundary 0 does not exist in session "empty" (last seq: none)', 'INVALID_BOUNDARY'))
|
.toThrow(new SessionForkError('fork boundary 0 does not exist in session "empty" (last seq: none)', 'INVALID_BOUNDARY'))
|
||||||
expect(ctx.sessions.get(SessionId('empty-child'))).toBeUndefined()
|
expect(ctx.sessions.get(SessionId('empty-child'))).toBeUndefined()
|
||||||
|
|
||||||
const source = ctx.sessions.create(SessionId('parent'))
|
const source = ctx.sessions.create(SessionId('parent'))
|
||||||
appendClosedTurn(source, 1)
|
appendClosedTurn(source, 1)
|
||||||
expect(() => sessions.fork({ source, boundary: -1, childSessionId: SessionId('negative') }))
|
expect(() => sessions.fork(source, -1, SessionId('negative')))
|
||||||
.toThrow(/non-negative safe integer/)
|
.toThrow(/non-negative safe integer/)
|
||||||
expect(() => sessions.fork({ source, boundary: 0.5, childSessionId: SessionId('fraction') }))
|
expect(() => sessions.fork(source, 0.5, SessionId('fraction')))
|
||||||
.toThrow(/non-negative safe integer/)
|
.toThrow(/non-negative safe integer/)
|
||||||
expect(() => sessions.fork({ source, boundary: Number.MAX_SAFE_INTEGER + 1, childSessionId: SessionId('unsafe') }))
|
expect(() => sessions.fork(source, Number.MAX_SAFE_INTEGER + 1, SessionId('unsafe')))
|
||||||
.toThrow(/non-negative safe integer/)
|
.toThrow(/non-negative safe integer/)
|
||||||
expect(() => sessions.fork({ source, boundary: source.seq, childSessionId: SessionId('past-end') }))
|
expect(() => sessions.fork(source, source.seq, SessionId('past-end')))
|
||||||
.toThrow(new SessionForkError(`fork boundary ${source.seq} does not exist in session "parent" (last seq: ${source.seq - 1})`, 'INVALID_BOUNDARY'))
|
.toThrow(new SessionForkError(`fork boundary ${source.seq} does not exist in session "parent" (last seq: ${source.seq - 1})`, 'INVALID_BOUNDARY'))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -151,7 +143,7 @@ describe('SessionStore.fork', () => {
|
|||||||
const mutableLog = (source as unknown as { log: SessionEvent[] }).log
|
const mutableLog = (source as unknown as { log: SessionEvent[] }).log
|
||||||
mutableLog[2] = { ...mutableLog[2]!, seq: 99 }
|
mutableLog[2] = { ...mutableLog[2]!, seq: 99 }
|
||||||
|
|
||||||
expect(() => sessions.fork({ source, boundary: 2, childSessionId: SessionId('corrupt-child') }))
|
expect(() => sessions.fork(source, 2, SessionId('corrupt-child')))
|
||||||
.toThrow(new SessionForkError('fork boundary 2 does not match a contiguous event seq in session "corrupt-parent"', 'INVALID_BOUNDARY'))
|
.toThrow(new SessionForkError('fork boundary 2 does not match a contiguous event seq in session "corrupt-parent"', 'INVALID_BOUNDARY'))
|
||||||
expect(ctx.sessions.get(SessionId('corrupt-child'))).toBeUndefined()
|
expect(ctx.sessions.get(SessionId('corrupt-child'))).toBeUndefined()
|
||||||
})
|
})
|
||||||
@@ -159,7 +151,7 @@ describe('SessionStore.fork', () => {
|
|||||||
it('rejects an unknown live session id', async () => {
|
it('rejects an unknown live session id', async () => {
|
||||||
const { sessions } = await setup()
|
const { sessions } = await setup()
|
||||||
|
|
||||||
expect(() => sessions.fork({ source: SessionId('missing') }))
|
expect(() => sessions.fork(SessionId('missing')))
|
||||||
.toThrow(new SessionForkError('session "missing" not found', 'SESSION_NOT_FOUND'))
|
.toThrow(new SessionForkError('session "missing" not found', 'SESSION_NOT_FOUND'))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -167,7 +159,7 @@ describe('SessionStore.fork', () => {
|
|||||||
const { sessions } = await setup()
|
const { sessions } = await setup()
|
||||||
const detached = new Session(SessionId('detached'))
|
const detached = new Session(SessionId('detached'))
|
||||||
|
|
||||||
expect(() => sessions.fork({ source: detached }))
|
expect(() => sessions.fork(detached))
|
||||||
.toThrow(new SessionForkError('session "detached" not found', 'SESSION_NOT_FOUND'))
|
.toThrow(new SessionForkError('session "detached" not found', 'SESSION_NOT_FOUND'))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -176,7 +168,7 @@ describe('SessionStore.fork', () => {
|
|||||||
ctx.sessions.create(SessionId('same-id'))
|
ctx.sessions.create(SessionId('same-id'))
|
||||||
const stale = new Session(SessionId('same-id'))
|
const stale = new Session(SessionId('same-id'))
|
||||||
|
|
||||||
expect(() => sessions.fork({ source: stale }))
|
expect(() => sessions.fork(stale))
|
||||||
.toThrow(new SessionForkError('session "same-id" is not the live store instance', 'SESSION_NOT_LIVE'))
|
.toThrow(new SessionForkError('session "same-id" is not the live store instance', 'SESSION_NOT_LIVE'))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -221,7 +213,7 @@ describe('SessionStore.fork', () => {
|
|||||||
const source = ctx.sessions.create(SessionId(`open-${lastType}`))
|
const source = ctx.sessions.create(SessionId(`open-${lastType}`))
|
||||||
const boundary = build(source)
|
const boundary = build(source)
|
||||||
|
|
||||||
expect(() => sessions.fork({ source, boundary }))
|
expect(() => sessions.fork(source, boundary))
|
||||||
.toThrow(new SessionForkError(`fork boundary ${boundary} in session "open-${lastType}" must be turn/end, got ${lastType}`, 'OPEN_TURN'))
|
.toThrow(new SessionForkError(`fork boundary ${boundary} in session "open-${lastType}" must be turn/end, got ${lastType}`, 'OPEN_TURN'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -232,7 +224,7 @@ describe('SessionStore.fork', () => {
|
|||||||
appendClosedTurn(source, 1)
|
appendClosedTurn(source, 1)
|
||||||
ctx.sessions.create(SessionId('child'))
|
ctx.sessions.create(SessionId('child'))
|
||||||
|
|
||||||
expect(() => sessions.fork({ source, childSessionId: SessionId('child') }))
|
expect(() => sessions.fork(source, undefined, SessionId('child')))
|
||||||
.toThrow(new SessionForkError('session "child" already exists', 'SESSION_ALREADY_EXISTS'))
|
.toThrow(new SessionForkError('session "child" already exists', 'SESSION_ALREADY_EXISTS'))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -242,7 +234,7 @@ describe('SessionStore.fork', () => {
|
|||||||
source.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
source.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||||
ctx.sessions.create(SessionId('child'))
|
ctx.sessions.create(SessionId('child'))
|
||||||
|
|
||||||
expect(() => sessions.fork({ source, childSessionId: SessionId('child') }))
|
expect(() => sessions.fork(source, undefined, SessionId('child')))
|
||||||
.toThrow(new SessionForkError('session "child" already exists', 'SESSION_ALREADY_EXISTS'))
|
.toThrow(new SessionForkError('session "child" already exists', 'SESSION_ALREADY_EXISTS'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => {
|
|||||||
const source = ctx.sessions.create(SessionId('persist-parent'), { meta: { cwd: '/workspace' } })
|
const source = ctx.sessions.create(SessionId('persist-parent'), { meta: { cwd: '/workspace' } })
|
||||||
appendClosedTurn(source)
|
appendClosedTurn(source)
|
||||||
|
|
||||||
const child = ctx.sessions.fork({ source, childSessionId: SessionId('persist-child') })
|
const child = ctx.sessions.fork(source, undefined, SessionId('persist-child'))
|
||||||
await ctx.parallel('session/flush', child)
|
await ctx.parallel('session/flush', child)
|
||||||
const loaded = await ctx.sessionPersistence.load(child.id)
|
const loaded = await ctx.sessionPersistence.load(child.id)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user