refactor: tool-todo registers the todos unit (init/apply/view); backscan removed
This commit is contained in:
@@ -24,7 +24,7 @@ The canonical result is `{ todos, counts: { pending, inProgress, completed } }`;
|
||||
|
||||
## Session projection
|
||||
|
||||
When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md)), this package registers the `todos` provider under an injected child: value = the latest `todo/write` snapshot backscanned from the in-memory log tail (whole list, last-wins), `null` before the first write. The key merges into `SessionProjectionMap` here (via the interface package's `/types` outlet); carriers serve it on the history tail page. Compositions without the registry are unaffected.
|
||||
When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md)), this package registers the `todos` projection unit under an injected child: `init` = `null` (no write yet), `apply` = take the whole list from each `todo/write` (last-wins; every other event returns the same state reference), `view` = identity, `stateVersion` = 1. The key merges into `SessionProjectionMap` here (via the interface package's `/types` outlet); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected.
|
||||
|
||||
## Export shape
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ import type { Context } from 'cordis'
|
||||
import { z } from 'zod'
|
||||
import type { ZodType } from 'zod'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import type { SessionEvent, TodoItem } from '@deepseek-ai/dsh-session'
|
||||
// Type-only: resolves ctx.sessionProjections for the optional provider child.
|
||||
import type { TodoItem } from '@deepseek-ai/dsh-session'
|
||||
// Type-only: resolves ctx.sessionProjections for the optional unit child.
|
||||
import type {} from '@deepseek-ai/dsh-session-projection'
|
||||
|
||||
declare module '@deepseek-ai/dsh-session-projection/types' {
|
||||
@@ -82,29 +81,20 @@ const todosProjectionSchema: ZodType<TodoItem[] | null> = z.union([
|
||||
z.null(),
|
||||
])
|
||||
|
||||
/**
|
||||
* Current whole todo list: the latest `todo/write` snapshot, backscanned from
|
||||
* the log tail (bounded: first hit terminates; the events live in memory).
|
||||
* `null` = no write yet.
|
||||
*/
|
||||
function currentTodos(agent: Agent): TodoItem[] | null {
|
||||
const events = agent.session.events
|
||||
for (let i = events.length - 1; i >= 0; i--) {
|
||||
const event = events[i] as SessionEvent
|
||||
if (event.type === 'todo/write') return event.data.todos
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** Register the `todo_write` tool on `ctx.tools` and, when the session-projection seam is composed, the `todos` provider. */
|
||||
/** Register the `todo_write` tool on `ctx.tools` and, when the session-projection seam is composed, the `todos` unit. */
|
||||
export function apply(ctx: Context): void {
|
||||
// The provider child activates only when a projection registry is composed
|
||||
// (headless assemblies without the seam stay unaffected).
|
||||
// The unit child activates only when a projection registry is composed
|
||||
// (headless assemblies without the seam stay unaffected). Pure last-wins
|
||||
// fold: state is the latest whole todo/write list, null before the first
|
||||
// write; every other event returns the same reference (no downstream work).
|
||||
ctx.inject(['sessionProjections'], (projectionCtx) => {
|
||||
projectionCtx.sessionProjections.register({
|
||||
projectionCtx.sessionProjections.register<'todos', TodoItem[] | null>({
|
||||
key: 'todos',
|
||||
schema: todosProjectionSchema,
|
||||
get: currentTodos,
|
||||
init: () => null,
|
||||
apply: (state, event) => (event.type === 'todo/write' ? event.data.todos : state),
|
||||
view: state => state,
|
||||
stateVersion: 1,
|
||||
})
|
||||
})
|
||||
ctx.tools.register(defineTool({
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* The `todos` projection provider (session-projection RFC knife 4 — the "a
|
||||
* fourth domain is just its own registrations" acceptance probe): mounting
|
||||
* tool-todo beside the registry serves the whole current list on the history
|
||||
* tail page with a consistent asOfSeq; before any write the value is null; a
|
||||
* tail page with a consistent asOfSeq (= last event seq); before any write the value is null; a
|
||||
* composition without tool-todo has no `todos` key; unmounting tool-todo
|
||||
* removes it (HMR safety). The carrier and framework are exercised unmodified.
|
||||
*/
|
||||
@@ -67,10 +67,10 @@ describe('todos projection provider', () => {
|
||||
seedMessage(bench.session)
|
||||
const projections = await bench.tailProjections()
|
||||
expect(projections?.values).toEqual({ todos: null })
|
||||
expect(projections?.asOfSeq).toBe(bench.session.seq)
|
||||
expect(projections?.asOfSeq).toBe(bench.session.seq - 1)
|
||||
})
|
||||
|
||||
it('serves the latest whole list after writes, asOfSeq = window tail seq', async () => {
|
||||
it('serves the latest whole list after writes, asOfSeq = last event seq', async () => {
|
||||
const bench = await harness(true)
|
||||
const session = bench.session
|
||||
seedMessage(session)
|
||||
@@ -84,7 +84,7 @@ describe('todos projection provider', () => {
|
||||
const projections = await bench.tailProjections()
|
||||
// Last-wins: the latest snapshot, whole.
|
||||
expect(projections?.values.todos).toEqual(second)
|
||||
expect(projections?.asOfSeq).toBe(session.seq)
|
||||
expect(projections?.asOfSeq).toBe(session.seq - 1)
|
||||
})
|
||||
|
||||
it('has no todos key when tool-todo is not composed', async () => {
|
||||
|
||||
Reference in New Issue
Block a user