Merge remote-tracking branch 'origin/master' into codex/enforce-tool-cancellation
# Conflicts: # docs/event-producer-consumer.md # examples/acp-agent/tests/snapshots/bash-spill/session.jsonl # examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl # examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl # examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl # examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl # packages/context/workspace-context/tests/workspace-context.spec.ts # packages/core/agent/src/index.ts # packages/support/invariants/tests/invariants.spec.ts # packages/ui/acp/src/index.ts # packages/ui/tui/tests/harness.ts # packages/ui/tui/tests/tui.spec.ts
This commit is contained in:
@@ -16,6 +16,7 @@ import type { Agent, AgentOptions } from './types.ts'
|
||||
|
||||
export * from './types.ts'
|
||||
export { agentInterruptReasonOf } from './cancellation.ts'
|
||||
export * from './llm-target.ts'
|
||||
export { agentEvents, assembleContextFor } from './dispatch.ts'
|
||||
export type { AgentEventDispatch, AgentSubjectEvent } from './dispatch.ts'
|
||||
|
||||
|
||||
35
packages/core/agent/src/invariant.ts
Normal file
35
packages/core/agent/src/invariant.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/** Package-owned agent lifecycle invariants. @module @deepseek-ai/dsh-agent/invariant */
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-agent'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'agent-invariant'
|
||||
/** Services required before the companion can register. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Install the agent contribution into its child registration fiber. */
|
||||
const install: InvariantInstaller = (ctx, fail) => {
|
||||
const lastStatus = new WeakMap<Agent, AgentStatus>()
|
||||
ctx.on('agent/status', (agent, status) => {
|
||||
const previous = lastStatus.get(agent)
|
||||
if (previous === status) {
|
||||
fail(`agent/status repeated ${status} (no-op transition)`)
|
||||
}
|
||||
if (previous === 'disposed') {
|
||||
fail(`agent/status left terminal state disposed → ${status}`)
|
||||
}
|
||||
lastStatus.set(agent, status)
|
||||
}, { global: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the agent invariant companion.
|
||||
* @param ctx - Cordis context carrying the invariant service.
|
||||
* @returns the installed registration's disposer after setup succeeds.
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
66
packages/core/agent/src/llm-target.ts
Normal file
66
packages/core/agent/src/llm-target.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Agent-scoped provider/model target snapshot shared by interactive front doors.
|
||||
* @module @deepseek-ai/dsh-agent/llm-target
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/** Complete provider/model route selected for one live agent. */
|
||||
export interface AgentLlmTarget {
|
||||
/** Registered provider route. */
|
||||
provider: string
|
||||
/** Provider-owned model id. */
|
||||
model: string
|
||||
}
|
||||
|
||||
/** Mutable selection plus the target captured for the current step. */
|
||||
export interface AgentLlmTargetRef {
|
||||
/** Target selected for the next step that enters prompt assembly. */
|
||||
current: AgentLlmTarget | undefined
|
||||
/** Target captured when the current step entered prompt assembly. */
|
||||
assembled: AgentLlmTarget | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Couple one mutable target to agent-scoped prompt assembly and request routing.
|
||||
* Prompt assembly snapshots the selected pair before delegating, then applies
|
||||
* both prompt variables and request config to that snapshot so a concurrent
|
||||
* switch takes effect on a later step instead of splitting the two surfaces.
|
||||
*
|
||||
* @param agentCtx - The target agent's scoped context.
|
||||
* @param target - Mutable selection owned by the calling front door.
|
||||
* @returns Disposer for both scoped waterfall listeners.
|
||||
*/
|
||||
export function installAgentLlmTarget(agentCtx: Context, target: AgentLlmTargetRef): () => void {
|
||||
const disposeAssembly = agentCtx.on('system-prompt/assemble', async (_assembly, _context, next) => {
|
||||
const selected = target.current
|
||||
const assembled = await next()
|
||||
target.assembled = selected
|
||||
if (selected === undefined) return assembled
|
||||
return {
|
||||
...assembled,
|
||||
variables: {
|
||||
...assembled.variables,
|
||||
provider: selected.provider,
|
||||
model: selected.model,
|
||||
},
|
||||
}
|
||||
})
|
||||
const disposeRequest = agentCtx.on(
|
||||
'agent/request',
|
||||
async (_agent, _turn, _step, _config, _signal, next): Promise<LlmCallConfig> => {
|
||||
const resolved = await next()
|
||||
const selected = target.assembled
|
||||
return selected === undefined ? resolved : {
|
||||
...resolved,
|
||||
provider: selected.provider,
|
||||
model: selected.model,
|
||||
}
|
||||
},
|
||||
)
|
||||
return () => {
|
||||
disposeAssembly()
|
||||
disposeRequest()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user