Merge master into codex/session-title

This commit is contained in:
Tianyi Cui
2026-07-21 22:59:41 +08:00
197 changed files with 3343 additions and 1227 deletions

View File

@@ -44,7 +44,7 @@ A goal mutation made during its round supersedes settlement of the older revisio
Activation is never inherited when this plugin loads over an existing agent. `GoalService.disarm()` removes process-local authority without changing durable phase, revision, or history; explicit human-authorized resume records the later reactivation. The same rule applies after session resume and fork through the goal domain's `agent/session-start` handling.
Cancellation is observe-before-act: the concrete loop emits `agent/cancel-requested` before clearing queues or aborting a step. The plugin durably pauses an active goal only when the cancellation owns a reserved or admitted goal attempt; cancellation of unrelated human work merely disarms process-local continuation. If the pause mutation fails, the driver falls back to disarming. Plugin teardown closes admission, disarms every live goal, cancels an admitted round, and awaits the driver plus agent quiescence while its event fence remains installed.
Cancellation is observe-before-act: the concrete loop emits `agent/cancel-requested` with its typed cause before clearing queues or aborting the turn. The plugin durably pauses an active goal only when the cancellation owns a reserved or admitted goal attempt; cancellation of unrelated human work merely disarms process-local continuation. If the pause mutation fails, the driver falls back to disarming. Plugin teardown closes admission, disarms every live goal, cancels an admitted round with the `parent` cause, and awaits the driver plus agent quiescence while its event fence remains installed.
## Model Experience

View File

@@ -313,7 +313,7 @@ export function apply(ctx: Context): void {
state.competingQueued = true
if (attempt?.phase === 'queued') attempt.stale = true
})
ctx.on('agent/cancel-requested', (agent, reason) => {
ctx.on('agent/cancel-requested', (agent, cause) => {
const state = stateFor(agent)
const attempt = state.attempt
state.attempt = undefined
@@ -325,7 +325,7 @@ export function apply(ctx: Context): void {
return
}
try {
applyOutcome(state, goal, { kind: 'pause', reason })
applyOutcome(state, goal, { kind: 'pause', reason: cause.kind })
} catch (error: unknown) {
ctx.logger.warn(`goal-session: could not pause cancelled goal for agent "${agent.id}": ${renderThrown(error)}`)
disarm(state)
@@ -399,7 +399,7 @@ export function apply(ctx: Context): void {
&& source.round === goal.roundsStarted + 1
}
ctx.on('agent/prompt-submit', async (agent, content, source, next): Promise<PromptDecision> => {
ctx.on('agent/prompt-submit', async (agent, content, source, _signal, next): Promise<PromptDecision> => {
if (!isGoalRoundSource(source)) return next()
const state = stateFor(agent)
let valid = false
@@ -449,7 +449,7 @@ export function apply(ctx: Context): void {
if (attempt !== undefined) {
attempt.stale = true
if (attempt.phase === 'admitted' && state.agent.status === 'running') {
state.agent.cancel('goal-session driver disposed')
state.agent.cancel({ kind: 'parent' })
}
waits.push(state.agent.whenIdle())
}

View File

@@ -26,7 +26,7 @@ export function classifyGoalRound(reason: TurnEndReason, durable: boolean): Goal
case 'completed':
return { kind: 'continue' }
case 'aborted':
return { kind: 'pause', reason: reason.reason ?? 'cancelled' }
return { kind: 'pause', reason: 'cancelled' }
case 'error': {
const { code, message } = reason.failure ?? reason
return code === 'RATE_LIMIT' || code === 'QUOTA'

View File

@@ -130,7 +130,6 @@ async function waitForRequests(adapter: ScriptedAdapter, count: number): Promise
describe('goal-round outcome policy', () => {
it.each([
[{ kind: 'completed' }, true, { kind: 'continue' }],
[{ kind: 'aborted', reason: 'operator stopped' }, true, { kind: 'pause', reason: 'operator stopped' }],
[{ 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' }],
@@ -255,7 +254,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, _content, source, next) => source.kind === 'goal'
test.ctx.on('agent/prompt-submit', (_agent, _content, source, _signal, next) => source.kind === 'goal'
? Promise.resolve({ kind: 'block', reason: 'deployment policy' })
: next())
test.ctx.goals.create(test.agent, { objective: 'respect policy' })
@@ -271,7 +270,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, _content, source, next) => source.kind === 'goal'
test.ctx.on('agent/prompt-submit', (_agent, _content, source, _signal, next) => source.kind === 'goal'
? Promise.resolve({ kind: 'block', reason: 'stop this round' })
: next())
test.ctx.on('goal/changed', (agent, change) => {
@@ -291,7 +290,7 @@ describe('same-session goal driving', () => {
const cancel = test.ctx.on('agent/queued', (agent, _content, info) => {
if (agent === test.agent && info.source.kind === 'goal') {
cancel()
agent.cancel('operator cancelled pending goal')
agent.cancel({ kind: 'user' })
}
})
test.ctx.goals.create(test.agent, { objective: 'do not start yet' })
@@ -309,7 +308,7 @@ describe('same-session goal driving', () => {
test.ctx.goals.create(test.agent, { objective: 'stop in flight' })
await waitForRequests(test.adapter, 1)
test.agent.cancel('operator stopped active goal')
test.agent.cancel({ kind: 'user' })
await test.agent.whenIdle()
const goal = await waitForGoal(test.ctx, test.agent, current => current?.phase === 'paused')
@@ -401,7 +400,7 @@ 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, _content, source, next) => {
test.ctx.on('agent/prompt-submit', (agent, _content, source, _signal, next) => {
if (source.kind === 'goal' && !edited) {
edited = true
const current = test.ctx.goals.get(agent)
@@ -577,7 +576,7 @@ 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, _content, source, next) => {
test.ctx.on('agent/prompt-submit', (_agent, _content, source, _signal, next) => {
if (source.kind === 'goal' && armed) {
armed = false
vi.spyOn(test.ctx.goals, 'get').mockImplementationOnce(() => {
@@ -609,7 +608,7 @@ describe('same-session goal driving', () => {
it('does not invent goal state when ordinary queued work is cancelled', async () => {
const test = await harness([])
test.agent.send([{ type: 'text', text: 'cancel ordinary work' }])
test.agent.cancel('ordinary cancellation')
test.agent.cancel({ kind: 'user' })
await test.agent.whenIdle()
expect(test.ctx.goals.get(test.agent)).toBeUndefined()
@@ -622,7 +621,7 @@ describe('same-session goal driving', () => {
await waitForRequests(test.adapter, 1)
const created = test.ctx.goals.create(test.agent, { objective: 'continue after inspection' })
test.agent.cancel('cancel the inspection')
test.agent.cancel({ kind: 'user' })
await test.agent.whenIdle()
expect(test.ctx.goals.get(test.agent)).toMatchObject({
@@ -642,7 +641,7 @@ describe('same-session goal driving', () => {
vi.spyOn(test.ctx.goals, 'pause').mockImplementationOnce(() => {
throw new Error('pause failed')
})
agent.cancel('cancel the reserved goal round')
agent.cancel({ kind: 'user' })
})
test.ctx.goals.create(test.agent, { objective: 'fail closed after cancellation' })
@@ -655,10 +654,10 @@ 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, _content, source, next) => {
test.ctx.on('agent/prompt-submit', (agent, _content, source, _signal, next) => {
if (source.kind === 'goal' && !cancelled) {
cancelled = true
agent.cancel('cancel from downstream admission policy')
agent.cancel({ kind: 'user' })
}
return next()
})

View File

@@ -13,6 +13,8 @@ import ToolRegistry from '@deepseek-ai/dsh-tools'
import type { ToolExecutionResult } from '@deepseek-ai/dsh-tools'
import * as toolGoal from '@deepseek-ai/dsh-tool-goal'
const testToolSignal = new AbortController().signal
interface StubAgent {
readonly agent: Agent
readonly session: Session
@@ -84,6 +86,7 @@ async function execute(
initiator: Agent | undefined = agent,
): Promise<ToolExecutionResult> {
const run = () => ctx.tools.execute({
signal: testToolSignal,
callId: CallId(`call-${Math.random()}`),
name,
arguments: args,
@@ -113,7 +116,7 @@ describe('goal tool registration and presentation', () => {
expect(['create_goal', 'get_goal', 'update_goal'].map(name => ctx.tools.get(name)?.name))
.toEqual(['create_goal', 'get_goal', 'update_goal'])
for (const name of ['create_goal', 'get_goal', 'update_goal']) {
expect(ctx.tools.executionMode({ callId: CallId(name), name, arguments: {} }))
expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId(name), name, arguments: {} }))
.toEqual({ kind: 'exclusive' })
}
const section = (await ctx.systemPrompt.assemble()).sections.find(item => item.name === 'tool:goal')
@@ -197,6 +200,7 @@ describe('goal tool execution authority', () => {
openTurn(root, { kind: 'user' })
const driverless = await ctx.tools.execute({
signal: testToolSignal,
callId: CallId('call-driverless'),
name: 'get_goal',
arguments: {},
@@ -326,7 +330,7 @@ describe('goal tool state transitions', () => {
goal_id: goal['id'], revision: goal['revision'], action: 'resume',
}, root.agent))
expect(goal).toMatchObject({ phase: 'active', revision: 4 })
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', 1)).toBeUndefined()
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', 1, testToolSignal)).toBeUndefined()
})
it('terminal-stops an autonomous completion but leaves a human pause interactive', async () => {
@@ -337,7 +341,7 @@ describe('goal tool state transitions', () => {
goal_id: created.id, revision: created.revision, action: 'pause',
}, root.agent)
expect(resultGoal(paused)).toMatchObject({ phase: 'paused' })
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', humanTurn)).toBeUndefined()
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', humanTurn, testToolSignal)).toBeUndefined()
const resumed = resultGoal(await execute(ctx, 'update_goal', {
goal_id: created.id, revision: 2, action: 'resume',
}, root.agent))
@@ -350,8 +354,8 @@ describe('goal tool state transitions', () => {
goal_id: created.id, revision: resumed['revision'], action: 'complete',
}, root.agent)
expect(resultGoal(complete)).toMatchObject({ phase: 'complete' })
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', roundTurn)).toEqual({ action: 'stop' })
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', roundTurn)).toBeUndefined()
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', roundTurn, testToolSignal)).toEqual({ action: 'stop' })
expect(await agentEvents(ctx, root.agent).serial('agent/turn-stop', roundTurn, testToolSignal)).toBeUndefined()
})
it('rearms a restored active goal only after a new direct human prompt', async () => {