test: migrate consumers to inbox and owned-run APIs

This commit is contained in:
_Kerman
2026-07-30 17:28:03 +08:00
parent a6baddaaac
commit 5a0d26a0e4
72 changed files with 716 additions and 1381 deletions

View File

@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
import AgentRegistry, {} from '@deepseek-ai/dsh-agent'
import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent'
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
import CommandService from '@deepseek-ai/dsh-commands'
import GoalService from '@deepseek-ai/dsh-goal'
@@ -34,6 +34,7 @@ function stubAgent(ctx: Context, id: string): { agent: Agent; session: Session }
id: session.id,
options: {},
session,
inbox: new Inbox(session),
ctx: new Context(),
get status() { return status },
followup: () => {},

View File

@@ -9,7 +9,7 @@ import type { GoalView } from '@deepseek-ai/dsh-goal'
import { createUserMessage, LlmAdapter, LlmError } from '@deepseek-ai/dsh-llm'
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { TurnEndReason, UserMessage } from '@deepseek-ai/dsh-session'
import type { UserMessage } from '@deepseek-ai/dsh-session'
import * as goalSession from '../src/index.ts'
type ScriptEntry = StreamChunk[] | Error | 'hang' | ((options: GenerateOptions) => StreamChunk[])
@@ -133,28 +133,6 @@ async function waitForRequests(adapter: ScriptedAdapter, count: number): Promise
}
describe('goal-round outcome policy', () => {
it.each([
[{ kind: 'completed' }, true, { kind: 'continue' }],
[{ kind: 'aborted' }, true, { kind: 'pause', reason: 'cancelled' }],
[{ kind: 'error', step: 1, message: 'slow down', code: 'RATE_LIMIT' }, true,
{ kind: 'blocked', code: 'usage-limited', message: 'slow down' }],
[{ kind: 'error', step: 1, failure: { message: 'credits exhausted', code: 'QUOTA' } }, true,
{ kind: 'blocked', code: 'usage-limited', message: 'credits exhausted' }],
[{ kind: 'error', step: 1, failure: { message: 'provider failed', code: 'SERVER' } }, true,
{ kind: 'blocked', code: 'turn-error', message: 'provider failed' }],
[{ kind: 'error', step: 1, message: 'broken' }, true,
{ kind: 'blocked', code: 'turn-error', message: 'broken' }],
[{ kind: 'max-tokens' }, true,
{ kind: 'blocked', code: 'max-tokens', message: 'model output reached max tokens' }],
[{ kind: 'disposed' }, true, { kind: 'disarm', reason: 'disposed' }],
[{ kind: 'interrupted' }, true, { kind: 'disarm', reason: 'interrupted' }],
[{ kind: 'completed' }, false, { kind: 'disarm', reason: 'durability-failed' }],
[{ kind: 'future-outcome' } as unknown as TurnEndReason, true,
{ kind: 'blocked', code: 'unknown-turn-outcome', message: 'unknown turn outcome: future-outcome' }],
] as const)('maps %j without abnormal automatic retry', (reason, durable, expected) => {
expect(goalSession.classifyGoalRound(reason, durable)).toEqual(expected)
})
it('renders the objective, round budget, authority boundary, and completion protocol', () => {
const goal: GoalView = {
id: GoalId('goal-prompt'),
@@ -259,7 +237,7 @@ describe('same-session goal driving', () => {
it('maps a downstream prompt veto to blocked without admitting the round', async () => {
const test = await harness([])
test.ctx.on('agent/prompt-submit', (_agent, message, _signal, next) => message.source.kind === 'goal'
test.ctx.on('agent/prompt-submit', (_agent, messages, _signal, next) => messages[0]?.source.kind === 'goal'
? Promise.resolve({ kind: 'block', reason: 'deployment policy' })
: next())
test.ctx.goals.create(test.agent, { objective: 'respect policy' })
@@ -274,7 +252,7 @@ describe('same-session goal driving', () => {
it('does not reserve again when a stopped-goal observer queues ordinary work', async () => {
const test = await harness([textResponse('human follow-up')])
test.ctx.on('agent/prompt-submit', (_agent, message, _signal, next) => message.source.kind === 'goal'
test.ctx.on('agent/prompt-submit', (_agent, messages, _signal, next) => messages[0]?.source.kind === 'goal'
? Promise.resolve({ kind: 'block', reason: 'stop this round' })
: next())
test.ctx.on('goal/changed', (agent, change) => {
@@ -382,8 +360,8 @@ describe('same-session goal driving', () => {
it('rechecks revision after downstream prompt hooks before admitting', async () => {
const test = await harness([textResponse('new revision')])
let edited = false
test.ctx.on('agent/prompt-submit', (agent, message, _signal, next) => {
if (message.source.kind === 'goal' && !edited) {
test.ctx.on('agent/prompt-submit', (agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && !edited) {
edited = true
const current = test.ctx.goals.get(agent)
if (current === undefined) throw new Error('missing goal during prompt edit')
@@ -489,8 +467,8 @@ describe('same-session goal driving', () => {
// attempt through cancel-requested) and THEN throws: the catch finds no
// matching reservation and must not reschedule a paused goal.
let fired = false
test.ctx.on('agent/prompt-submit', async (agent, message, _signal, next) => {
if (message.source.kind === 'goal' && !fired) {
test.ctx.on('agent/prompt-submit', async (agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && !fired) {
fired = true
agent.cancel({ kind: 'user' })
throw new Error('hook cancelled then exploded')
@@ -513,8 +491,8 @@ describe('same-session goal driving', () => {
// Registered after goal-session's own listener: the throw propagates back
// through goal-session's next() await, dropping the whole admission.
let threw = false
test.ctx.on('agent/prompt-submit', async (_agent, message, _signal, next) => {
if (message.source.kind === 'goal' && !threw) {
test.ctx.on('agent/prompt-submit', async (_agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && !threw) {
threw = true
throw new Error('downstream admission hook exploded')
}
@@ -660,8 +638,8 @@ describe('same-session goal driving', () => {
it('fails a post-hook read closed before the prompt can enter history', async () => {
const test = await harness([])
let armed = true
test.ctx.on('agent/prompt-submit', (_agent, message, _signal, next) => {
if (message.source.kind === 'goal' && armed) {
test.ctx.on('agent/prompt-submit', (_agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && armed) {
armed = false
vi.spyOn(test.ctx.goals, 'get').mockImplementationOnce(() => {
throw new Error('post-hook projection failed')
@@ -735,8 +713,8 @@ describe('same-session goal driving', () => {
it('blocks admission when downstream cancellation clears the reservation', async () => {
const test = await harness([])
let cancelled = false
test.ctx.on('agent/prompt-submit', (agent, message, _signal, next) => {
if (message.source.kind === 'goal' && !cancelled) {
test.ctx.on('agent/prompt-submit', (agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && !cancelled) {
cancelled = true
agent.cancel({ kind: 'user' })
}
@@ -804,8 +782,8 @@ describe('same-session goal driving', () => {
it('leaves a queued reservation pending when the driver runs before its turn settles', async () => {
const test = await harness([textResponse('settled later')])
let woken = false
test.ctx.on('agent/prompt-submit', async (_agent, message, _signal, next) => {
if (message.source.kind === 'goal' && !woken) {
test.ctx.on('agent/prompt-submit', async (_agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && !woken) {
woken = true
// A concurrent driver pass must observe the still-unsettled attempt
// and yield rather than double-book or clear the reservation.
@@ -929,8 +907,8 @@ describe('same-session goal driving', () => {
it('does not re-block a goal the downstream veto already saw cancelled', async () => {
const test = await harness([])
let vetoed = false
test.ctx.on('agent/prompt-submit', (agent, message, _signal, next) => {
if (message.source.kind === 'goal' && !vetoed) {
test.ctx.on('agent/prompt-submit', (agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && !vetoed) {
vetoed = true
agent.cancel({ kind: 'user' })
return Promise.resolve<PromptDecision>({ kind: 'block', reason: 'cancelled by policy' })
@@ -952,8 +930,8 @@ describe('same-session goal driving', () => {
it('awaits an unadmitted reservation stuck in admission during teardown without cancelling', async () => {
const test = await harness([])
let release: (() => void) | undefined
test.ctx.on('agent/prompt-submit', async (_agent, message, _signal, next) => {
if (message.source.kind === 'goal' && release === undefined) {
test.ctx.on('agent/prompt-submit', async (_agent, messages, _signal, next) => {
if (messages[0]?.source.kind === 'goal' && release === undefined) {
await new Promise<void>((resolve) => { release = resolve })
}
return next()

View File

@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import AgentRegistry, { agentEvents } from '@deepseek-ai/dsh-agent'
import AgentRegistry, { agentEvents, Inbox } from '@deepseek-ai/dsh-agent'
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
import { createUserMessage, HarnessError, type ContentBlock, type MessageSource } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionId, type UserMessage } from '@deepseek-ai/dsh-session'
@@ -44,6 +44,7 @@ function stubAgentForSession(session: Session): StubAgent {
id,
options: {},
session,
inbox: new Inbox(session),
ctx: new Context(),
get status() { return status },
followup: () => {},

View File

@@ -52,13 +52,7 @@ describe('goal stream invariants', () => {
source: changeSource,
}), { surfaceOp: 'append' })
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
session.append('turn/start', {
turn: 2,
trigger: {
kind: 'message',
source: { kind: 'goal', goalId: change.goal.id, revision: 1, round: 1 },
},
})
session.append('turn/start', { turn: 2 })
expect(() => {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'continue' }],
@@ -102,13 +96,7 @@ describe('goal stream invariants', () => {
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(GoalInvariantCompanion)
session.append('turn/start', {
turn: 2,
trigger: {
kind: 'message',
source: { kind: 'goal', goalId: change.goal.id, revision: 1, round: 1 },
},
})
session.append('turn/start', { turn: 2 })
expect(() => {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'continue after load' }],

View File

@@ -10,7 +10,7 @@
import { describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent'
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { UserMessage } from '@deepseek-ai/dsh-session'
@@ -35,6 +35,7 @@ function liveAgent(ctx: Context, session: Session): Agent {
id: session.id,
options: {},
session,
inbox: new Inbox(session),
ctx,
get status() { return status },
followup: () => {},

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
import AgentRegistry, { agentEvents } from '@deepseek-ai/dsh-agent'
import AgentRegistry, { agentEvents, Inbox } from '@deepseek-ai/dsh-agent'
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
import GoalService, { GoalId } from '@deepseek-ai/dsh-goal'
import type { GoalRef } from '@deepseek-ai/dsh-goal'
@@ -29,6 +29,7 @@ function stubAgent(rawId: string, supplied?: Session): StubAgent {
id: session.id,
options: {},
session,
inbox: new Inbox(session),
get status() { return status },
ctx: new Context(),
followup: () => {},