Merge commit 'refs/codex-unblock/20260724/pr591-base-current' into worktree/unblock-pr-591-ci-20260724

This commit is contained in:
Tianyi Cui
2026-07-24 20:32:02 +08:00
200 changed files with 2888 additions and 1165 deletions

View File

@@ -18,7 +18,7 @@ An autonomous goal round that successfully reports `complete` or `blocked` contr
Execution requires the exact live `exec.agent`, its inherited `AgentRegistry` initiator, running status, and an open turn. Create, edit, pause, and resume additionally require an accepted `{ kind: 'user' }` message or steering event in a runtime-root agent's current turn. Durable fork lineage does not demote a resumed root; live subagent ownership does.
`{ kind: 'user' }` is a host attestation. `Agent.send()` and `steer()` assign it when their caller omits a source, so plugins, schedulers, and other non-human producers must pass their own source rather than inheriting human authority.
`{ kind: 'user' }` is a host attestation. `Agent.followup()` and `steer()` assign it when their caller omits a source, so plugins, schedulers, and other non-human producers must pass their own source rather than inheriting human authority.
Complete and blocked also accept the exact current goal round: a goal-sourced `user/message` whose id, revision, and round equal the folded current goal. A goal-round blocked call is mechanically rejected until `blockedAfterConsecutiveRounds`; the model judges whether the same condition actually persisted and must describe it in `blocked_reason`. Direct human authority may stop a goal immediately.

View File

@@ -64,7 +64,7 @@ export function goalToolExecution(ctx: Context, exec: ToolRunContext): GoalToolE
/**
* Whether host-attested human input appears in the current root-agent turn.
* An omitted `Agent.send()` / `steer()` source resolves to `user`, so non-human
* An omitted `Agent.followup()` / `steer()` source resolves to `user`, so non-human
* producers must supply their own source rather than inheriting this authority.
*/
function hasDirectHumanInput(ctx: Context, execution: GoalToolExecution): boolean {

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, AgentMessageId } from '@deepseek-ai/dsh-agent'
import type { Agent, AgentStatus, InjectOptions } from '@deepseek-ai/dsh-agent'
import GoalService, { GoalId } from '@deepseek-ai/dsh-goal'
import type { GoalRef } from '@deepseek-ai/dsh-goal'
@@ -31,16 +31,19 @@ function stubAgent(rawId: string, supplied?: Session): StubAgent {
session,
get status() { return status },
ctx: new Context(),
send() {},
steer() {},
followup: () => AgentMessageId('stub'),
queue: () => AgentMessageId('stub'),
steer: () => AgentMessageId('stub'),
inject(content: ContentBlock[], options?: InjectOptions) {
const source = options?.source ?? { kind: 'user' }
session.append('context/message', {
const source = options?.source ?? { kind: 'plugin', plugin: '' }
session.append('user/message', {
content,
source,
...options?.meta === undefined ? {} : { meta: options.meta },
}, { surfaceOp: 'append' })
return AgentMessageId('stub')
},
send: () => AgentMessageId('stub'),
cancel() {},
whenIdle() { return Promise.resolve() },
}
@@ -228,7 +231,9 @@ describe('goal tool execution authority', () => {
it('rejects stale agent objects and agents outside running status through the executor', async () => {
const { ctx, root } = await harness()
openTurn(root, { kind: 'user' })
const stale = { ...root.agent }
// A distinct agent object over root's exact session: same id, not the live
// registered instance, so the executor must reject it.
const stale = stubAgent('goal-tool-stale', root.agent.session).agent
const staleResult = await execute(ctx, 'get_goal', {}, stale, stale)
expect(staleResult.error?.info?.code).toBe('GOAL_TOOL_DRIVER_REQUIRED')