Steer, inject, and followup now land as durable user/message events on the session surface; the steering/message event type and its ConversationNode kind are removed from the client projection. Update tests, docs, generated catalogs, and agent notes to match, and align the steering e2e fixture and prompt inventory assertions with the durable user/message landing.
109 lines
4.4 KiB
TypeScript
109 lines
4.4 KiB
TypeScript
/** Execution-time authority checks for the model-facing goal tools. */
|
|
|
|
import type { Context } from 'cordis'
|
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
import type { GoalView } from '@deepseek-ai/dsh-goal'
|
|
import { HarnessError } from '@deepseek-ai/dsh-llm'
|
|
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
|
import type { ToolRunContext } from '@deepseek-ai/dsh-tools'
|
|
|
|
type TurnStartEvent = Extract<SessionEvent, { type: 'turn/start' }>
|
|
|
|
/** Current open turn plus the events accepted after its start boundary. */
|
|
export interface GoalToolExecution {
|
|
readonly agent: Agent
|
|
readonly start: TurnStartEvent
|
|
readonly events: readonly SessionEvent[]
|
|
}
|
|
|
|
/** Hard authority granted to one state-changing call. */
|
|
export type GoalToolAuthority =
|
|
| { readonly kind: 'direct-human' }
|
|
| { readonly kind: 'goal-round'; readonly goal: GoalView }
|
|
|
|
/** Throw one structured tool-policy failure. */
|
|
function reject(message: string, code = 'GOAL_TOOL_AUTHORITY_REQUIRED'): never {
|
|
throw new HarnessError(message, code)
|
|
}
|
|
|
|
/** Locate the open turn enclosing a model tool call. */
|
|
function openTurn(agent: Agent): { start: TurnStartEvent; events: readonly SessionEvent[] } {
|
|
const events = agent.session.events
|
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
const boundary = events[index]
|
|
if (boundary?.type === 'turn/end') {
|
|
reject('goal tools require an open model turn', 'GOAL_TOOL_DRIVER_REQUIRED')
|
|
}
|
|
if (boundary?.type === 'turn/start') {
|
|
return { start: boundary, events: events.slice(index + 1) }
|
|
}
|
|
}
|
|
return reject('goal tools require an open model turn', 'GOAL_TOOL_DRIVER_REQUIRED')
|
|
}
|
|
|
|
/**
|
|
* Resolve and authenticate the calling agent and its driver boundary.
|
|
* @param ctx - Context carrying the live agent registry.
|
|
* @param exec - Tool execution metadata supplied by the registry.
|
|
* @returns The authenticated agent and its current turn window.
|
|
*/
|
|
export function goalToolExecution(ctx: Context, exec: ToolRunContext): GoalToolExecution {
|
|
const agent = exec.agent
|
|
if (agent === undefined) {
|
|
return reject('goal tools require a calling agent', 'GOAL_TOOL_AGENT_REQUIRED')
|
|
}
|
|
if (ctx.agents.get(agent.id) !== agent || agent.status !== 'running'
|
|
|| ctx.agents.currentInitiator() !== agent) {
|
|
return reject(
|
|
'goal tools require the exact live calling agent inside its active driver',
|
|
'GOAL_TOOL_DRIVER_REQUIRED',
|
|
)
|
|
}
|
|
return { agent, ...openTurn(agent) }
|
|
}
|
|
|
|
/**
|
|
* Whether host-attested human input appears in the current root-agent turn.
|
|
* 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 {
|
|
if (!ctx.agents.roots().includes(execution.agent)) return false
|
|
return execution.events.some(event =>
|
|
event.type === 'user/message' && event.data.source.kind === 'user')
|
|
}
|
|
|
|
/** Whether this turn is the current goal's exact admitted round. */
|
|
function isMatchingGoalRound(execution: GoalToolExecution, goal: GoalView): boolean {
|
|
return execution.events.some(event => event.type === 'user/message'
|
|
&& event.data.source.kind === 'goal'
|
|
&& event.data.source.goalId === goal.id
|
|
&& event.data.source.revision === goal.revision
|
|
&& event.data.source.round === goal.roundsStarted)
|
|
}
|
|
|
|
/**
|
|
* Require authority originating in a human message accepted by a runtime root.
|
|
* @param ctx - Context carrying the live agent graph.
|
|
* @param execution - Authenticated current tool execution.
|
|
*/
|
|
export function requireDirectHuman(ctx: Context, execution: GoalToolExecution): void {
|
|
if (hasDirectHumanInput(ctx, execution)) return
|
|
reject('this goal operation requires a direct human turn on a top-level agent')
|
|
}
|
|
|
|
/**
|
|
* Resolve completion authority from either direct human input or the exact goal round.
|
|
* @param ctx - Context carrying live agents and goal state.
|
|
* @param execution - Authenticated current tool execution.
|
|
* @returns The direct-human or exact-goal-round authority grant.
|
|
*/
|
|
export function completionAuthority(ctx: Context, execution: GoalToolExecution): GoalToolAuthority {
|
|
if (hasDirectHumanInput(ctx, execution)) return { kind: 'direct-human' }
|
|
const goal = ctx.goals.get(execution.agent)
|
|
if (goal !== undefined && isMatchingGoalRound(execution, goal)) {
|
|
return { kind: 'goal-round', goal }
|
|
}
|
|
return reject('complete and blocked require a direct human turn or the current goal round')
|
|
}
|