Merge remote-tracking branch 'origin/master' into feat/plan-mode
Master arrivals re-grafted onto the plan-mode surfaces: - Package invariant runtime contracts: every package owns ./invariant — added the dsh-mode companion (mode/set payload validation: non-empty trimmed name), its unit spec, exports/peer wiring, and the mode group in the tsconfig invariant paths glob. - Interception waterfalls gained an AbortSignal parameter (agent/prompt-submit, agent/turn-continuation) and ToolExecutionInput.signal became required: listeners and test dispatch updated; the exit tool now forwards exec.signal unconditionally. - TUI question dialog: master's position/unanswered header layout kept, the plan-review detail block re-grafted between question and answers. - LSP capability family arrived: lsp tool joins the catalog pin, dsh-lsp examples deps beside dsh-mode; lsp-definition snapshot refreshed with the mode command and modes advertisement. - docs/architecture.md and packages/README.md regenerated tables re-grafted (ctx.modes row, mode package row) and condensed within word ceilings. - examples/acp-agent cordis.yml: kept the deployment-owned plan instructions (incl. the conversational-agreement sentence) beside master's routed-model compaction comment; snapshot expected outputs refreshed keyless and noise fixtures restored.
This commit is contained in:
@@ -15,7 +15,7 @@ describe('turnEndToStopReason', () => {
|
||||
it('maps every known TurnEndReason kind to a legal StopReason', () => {
|
||||
expect(turnEndToStopReason({ kind: 'completed' })).toBe('end_turn')
|
||||
expect(turnEndToStopReason({ kind: 'max-tokens' })).toBe('max_tokens')
|
||||
expect(turnEndToStopReason({ kind: 'aborted', reason: 'x' })).toBe('cancelled')
|
||||
expect(turnEndToStopReason({ kind: 'aborted' })).toBe('cancelled')
|
||||
expect(turnEndToStopReason({ kind: 'disposed' })).toBe('cancelled')
|
||||
expect(turnEndToStopReason({ kind: 'rejected', reason: 'blocked by hook' })).toBe('cancelled')
|
||||
expect(turnEndToStopReason({ kind: 'error', step: 1, message: 'boom' })).toBe('end_turn')
|
||||
|
||||
@@ -8,7 +8,10 @@ import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import InvariantService from '@deepseek-ai/dsh-invariants'
|
||||
import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant'
|
||||
import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant'
|
||||
import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant'
|
||||
import ApprovalService from '@deepseek-ai/dsh-user-approval'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
|
||||
@@ -25,6 +28,13 @@ class SandboxedLocalExecutor extends LocalBashExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
async function mountInvariants(ctx: BridgeHarness['ctx']): Promise<void> {
|
||||
await ctx.plugin(InvariantService)
|
||||
await ctx.plugin(SessionInvariant)
|
||||
await ctx.plugin(AgentInvariant)
|
||||
await ctx.plugin(AgentLoopInvariant)
|
||||
}
|
||||
|
||||
function permissionOption(currentValue: string): object {
|
||||
return {
|
||||
id: 'permission',
|
||||
@@ -76,7 +86,7 @@ describe('acp bridge — session config options', () => {
|
||||
async function presetStack(options: { script?: NonNullable<Parameters<typeof makeBridgeHarness>[0]>['script'] } = {}): Promise<BridgeHarness> {
|
||||
const harness = await makeBridgeHarness({ storageDir, ...options.script !== undefined ? { script: options.script } : {} })
|
||||
// Make an out-of-turn switch fail in this suite.
|
||||
await harness.ctx.plugin(Invariants)
|
||||
await mountInvariants(harness.ctx)
|
||||
await harness.ctx.plugin(SandboxedLocalExecutor, { timeoutMs: 10_000 })
|
||||
await harness.ctx.plugin(ApprovalService)
|
||||
await harness.ctx.plugin(PermissionService)
|
||||
@@ -176,7 +186,7 @@ describe('acp bridge — session config options', () => {
|
||||
const { sessionId } = await h.client.newSession({ cwd: process.cwd(), mcpServers: [] })
|
||||
const agent = h.ctx.agents.list()[0]
|
||||
if (agent === undefined) throw new Error('expected an agent')
|
||||
agent.ctx.on('agent/request', async (_agent, _turn, _step, callConfig, _next) => ({
|
||||
agent.ctx.on('agent/request', async (_agent, _turn, _step, callConfig, _signal, _next) => ({
|
||||
...callConfig,
|
||||
provider: 'mock',
|
||||
model: 'mock',
|
||||
|
||||
@@ -325,6 +325,10 @@ describe('acp bridge — turn outcomes', () => {
|
||||
await harness.client.cancel({ sessionId })
|
||||
const res = await promptDone
|
||||
expect(res.stopReason).toBe('cancelled')
|
||||
const agent = harness.ctx.agents.get(SessionId(sessionId))!
|
||||
await agent.whenIdle()
|
||||
const turnEnd = agent.session.events.findLast(event => event.type === 'turn/end')
|
||||
expect(turnEnd?.type === 'turn/end' && turnEnd.data.reason).toEqual({ kind: 'aborted' })
|
||||
})
|
||||
|
||||
it('cancel right after prompt settles cancelled and leaves the agent idle, no leaked turn', async () => {
|
||||
|
||||
Reference in New Issue
Block a user