mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
125 lines
5.2 KiB
TypeScript
125 lines
5.2 KiB
TypeScript
/**
|
|
* 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 (= 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.
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
import { Context } from '@deepseek-ai/cordis'
|
|
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
import SessionStore from '@deepseek-ai/dsh-session'
|
|
import type { Session, TodoItem } from '@deepseek-ai/dsh-session'
|
|
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
|
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
|
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
|
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
|
import type { RpcRequest } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
|
|
import { RpcId } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
|
|
import { createApiProxy } from '@deepseek-ai/dsh-host-apiproxy'
|
|
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
|
|
|
|
let nextRpc = 1
|
|
function request<P>(payload: P): RpcRequest<P> {
|
|
return { rpcId: RpcId(`todo-proj-${String(nextRpc++)}`), payload }
|
|
}
|
|
|
|
interface Bench {
|
|
ctx: Context
|
|
session: Session
|
|
tailProjections(): Promise<{ asOfSeq: number; values: Record<string, unknown> } | undefined>
|
|
}
|
|
|
|
async function harness(withTodoTool: boolean): Promise<Bench> {
|
|
const ctx = new Context()
|
|
await ctx.plugin(SessionStore)
|
|
await ctx.plugin(SystemPrompt, { persona: '' })
|
|
await ctx.plugin(ToolRegistry)
|
|
await ctx.plugin(UserInteractionService)
|
|
await ctx.plugin(AgentRegistry)
|
|
await ctx.plugin(SessionProjectionRegistry)
|
|
if (withTodoTool) await ctx.plugin(ToolTodo, { allowParallelInProgress: true })
|
|
const session = ctx.sessions.create()
|
|
ctx.agents.register({ id: session.id, session, status: 'idle', ctx } as Agent)
|
|
const api = createApiProxy(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
|
|
return {
|
|
ctx,
|
|
session,
|
|
async tailProjections() {
|
|
const response = await api.sessions.history(request({ sessionId: session.id }))
|
|
if (!response.result.ok) throw new Error('history failed')
|
|
return response.result.value.projections
|
|
},
|
|
}
|
|
}
|
|
|
|
/** One paginable message so the tail page is non-degenerate. */
|
|
function seedMessage(session: Session): void {
|
|
session.append('user/message', createUserMessage({
|
|
content: [{ type: 'text', text: 'hi' }],
|
|
source: { kind: 'user' },
|
|
}), { surfaceOp: 'append' })
|
|
}
|
|
|
|
describe('todos projection provider', () => {
|
|
it('serves null before the first todo/write', async () => {
|
|
const bench = await harness(true)
|
|
seedMessage(bench.session)
|
|
const projections = await bench.tailProjections()
|
|
expect(projections?.values).toEqual({ todos: null })
|
|
expect(projections?.asOfSeq).toBe(bench.session.seq - 1)
|
|
})
|
|
|
|
it('serves the latest whole list after writes, asOfSeq = last event seq', async () => {
|
|
const bench = await harness(true)
|
|
const session = bench.session
|
|
seedMessage(session)
|
|
const first: TodoItem[] = [{ content: 'a', status: 'pending' }]
|
|
const second: TodoItem[] = [
|
|
{ content: 'a', status: 'completed' },
|
|
{ content: 'b', status: 'in_progress' },
|
|
]
|
|
session.append('todo/write', { todos: first })
|
|
session.append('todo/write', { todos: second })
|
|
const projections = await bench.tailProjections()
|
|
// Last-wins: the latest snapshot, whole.
|
|
expect(projections?.values.todos).toEqual(second)
|
|
expect(projections?.asOfSeq).toBe(session.seq - 1)
|
|
})
|
|
|
|
it('clears the standing plan on the next turn/start (turn/end keeps it)', async () => {
|
|
const bench = await harness(true)
|
|
const session = bench.session
|
|
seedMessage(session)
|
|
const list: TodoItem[] = [{ content: 'done', status: 'completed' }]
|
|
session.append('todo/write', { todos: list })
|
|
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
|
expect((await bench.tailProjections())?.values.todos).toEqual(list)
|
|
session.append('turn/start', { turn: 1 })
|
|
const cleared = await bench.tailProjections()
|
|
expect(cleared?.values.todos).toBeNull()
|
|
expect(cleared?.asOfSeq).toBe(session.seq - 1)
|
|
})
|
|
|
|
it('has no todos key when tool-todo is not composed', async () => {
|
|
const bench = await harness(false)
|
|
seedMessage(bench.session)
|
|
const projections = await bench.tailProjections()
|
|
expect(projections).toBeDefined()
|
|
expect('todos' in (projections?.values ?? {})).toBe(false)
|
|
})
|
|
|
|
it('drops the key when the tool-todo fiber unloads (HMR safety)', async () => {
|
|
const bench = await harness(false)
|
|
seedMessage(bench.session)
|
|
const fiber = await bench.ctx.plugin(ToolTodo, { allowParallelInProgress: true })
|
|
expect((await bench.tailProjections())?.values).toEqual({ todos: null })
|
|
await fiber.dispose()
|
|
expect('todos' in ((await bench.tailProjections())?.values ?? {})).toBe(false)
|
|
})
|
|
})
|