refactor: apply repository naming contract

Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
Tianyi Cui
2026-08-13 00:36:22 +08:00
parent 101df7cf58
commit a2d0f7f411
3281 changed files with 21730 additions and 21592 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/plan/plan-mode/README.md
README.md: b4e890f6b11998879119657ee73eb24826c101e1
README.zh.md: 784f9f9d98499c0b830836299846872cd49cc1b4
README.md: 7171997406ea43487762d9947d07426df400c78a
README.zh.md: 5c04cdabc293c9abda6bb5e77f596715ee137c6f

View File

@@ -12,7 +12,7 @@ Logged, per-agent plan collaboration state with deployment-owned guidance, direc
## Model and human interactions
While active, `plan:policy` renders the configured `section`. The plugin always registers `exit_plan_mode`, keeping tool schemas stable across the transition; its execute path accepts only active plan mode and leaves it only after an exact user approval through `ctx.userInteraction`.
While active, `plan:policy` renders the configured `section`. The plugin always registers `exit_plan_mode`, keeping tool schemas stable across the transition; its execute path accepts only active plan mode and leaves it only after an exact user approval through `ctx.userQuestions`.
The review question declares the `plan-review` presentation intent, naming `Approve` as the label that approves it, so a capable UI presents the plan as a decision instead of a generic question; the answer the tool reads is the same either way. A dismissed review — the user closing the request to speak instead — is reported to the model as such, telling it to stay in plan mode and wait for the message; every other review failure keeps the seam's own message.
@@ -83,7 +83,7 @@ The [`exit_plan_mode` schema](../../../docs/tool-catalog.md#deepseek-aidsh-plan-
#### Token effect
The stable schema is paid according to ToolRegistry mode, and each plan argument and review result remains in conversation history.
The stable schema is paid according to ToolRuntime mode, and each plan argument and review result remains in conversation history.
#### KV Cache effect

View File

@@ -12,7 +12,7 @@
## 模型与人类交互
激活时,`plan:policy` 会渲染已配置的 `section`。插件始终注册 `exit_plan_mode`,使工具 schema 在转换期间保持稳定;其 execute 路径只接受已激活的 plan mode且只有通过 `ctx.userInteraction` 获得用户明确批准后才退出。
激活时,`plan:policy` 会渲染已配置的 `section`。插件始终注册 `exit_plan_mode`,使工具 schema 在转换期间保持稳定;其 execute 路径只接受已激活的 plan mode且只有通过 `ctx.userQuestions` 获得用户明确批准后才退出。
评审问题声明 `plan-review` 呈现意图,并指名 `Approve` 为表示批准的标签,因此有能力的 UI 会把计划呈现为一次决定而非通用问题;两种情况下该工具读到的回答完全相同。放弃审阅——用户关闭请求,转而发言——会如实报告给模型,要求它留在 plan mode 中等待那条消息;其余每一种评审失败都保留 seam 自身的消息。
@@ -83,7 +83,7 @@ You are in plan mode. Explore and design before presenting the complete plan thr
#### Token 影响
稳定 schema 的成本取决于 ToolRegistry mode每次传入的 plan 参数和评审结果都会保留在对话历史中。
稳定 schema 的成本取决于 ToolRuntime mode每次传入的 plan 参数和评审结果都会保留在对话历史中。
#### KV Cache 影响

View File

@@ -49,7 +49,7 @@
"@deepseek-ai/dsh-session-projection": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@deepseek-ai/dsh-user-interaction": "workspace:^",
"@deepseek-ai/dsh-user-questions": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
},
"peerDependenciesMeta": {
@@ -71,7 +71,7 @@
"@deepseek-ai/dsh-session-projection": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@deepseek-ai/dsh-user-interaction": "workspace:^",
"@deepseek-ai/dsh-user-questions": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
}
}

View File

@@ -31,7 +31,7 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { Session, SessionEvent, UserMessage } from '@deepseek-ai/dsh-session'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-system-prompt'
import { UserInteractionError } from '@deepseek-ai/dsh-user-interaction'
import { UserQuestionError } from '@deepseek-ai/dsh-user-questions'
// Type-only edge: resolves `ctx.commands` for the optional command child.
import type {} from '@deepseek-ai/dsh-commands'
// Type-only: resolves ctx.sessionProjections for the optional unit child.
@@ -56,7 +56,7 @@ declare module '@deepseek-ai/dsh-session/types' {
declare module '@deepseek-ai/cordis' {
interface Context {
planMode: PlanModeService
planMode: PlanModeController
}
}
@@ -181,7 +181,7 @@ function planModeAtLastHeader(events: readonly SessionEvent[]): boolean | undefi
* the `plan:policy` section, the `/plan` command, and the stable exit tool.
* UIs observe committed flips through `session/event`; there is no live mirror.
*/
export class PlanModeService extends Service {
export class PlanModeController extends Service {
static inject = ['tools', 'systemPrompt']
/** Validated deployment-owned guidance. */
@@ -327,9 +327,9 @@ export class PlanModeService extends Service {
if (!/^#\s+\S/.test(args.plan.trim())) {
throw new Error(`${EXIT_PLAN_MODE} requires a non-empty markdown plan starting with a # heading`)
}
const interaction = ctx.get('userInteraction')
const interaction = ctx.get('userQuestions')
if (interaction === undefined) {
throw new Error('no user-interaction channel is available to review the plan; ask the user to switch the session mode instead')
throw new Error('no user-questions channel is available to review the plan; ask the user to switch the session mode instead')
}
const answer = await interaction.ask({
questions: [{
@@ -354,7 +354,7 @@ export class PlanModeService extends Service {
// generic channel message names ask_user_question, which the model
// never called. An abort (turn cancel, provider teardown) keeps its
// own message — there is no user to wait for.
if (cause instanceof UserInteractionError && cause.code === 'ASK_CANCELLED') {
if (cause instanceof UserQuestionError && cause.code === 'ASK_CANCELLED') {
throw new Error('The user dismissed the plan review to speak instead; '
+ 'stay in plan mode, stop here, and wait for their message.')
}
@@ -474,4 +474,4 @@ export class PlanModeService extends Service {
}
}
export default PlanModeService
export default PlanModeController

View File

@@ -1,12 +1,12 @@
import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import LlmService, { createUserMessage, type StreamChunk } from '@deepseek-ai/dsh-llm'
import LlmRuntime, { createUserMessage, type StreamChunk } from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import ToolRuntime, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import PlanModeService, { foldPlanMode } from '@deepseek-ai/dsh-plan-mode'
import PlanModeController, { foldPlanMode } from '@deepseek-ai/dsh-plan-mode'
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
const PLAN_CONFIG = { section: 'Test plan mode instructions.' }
@@ -21,13 +21,13 @@ const PLAN_CONFIG = { section: 'Test plan mode instructions.' }
*/
async function harness(adapter: MockAdapter): Promise<Context> {
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(LlmRuntime)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(ToolRuntime)
await ctx.plugin(AgentRegistry)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(PlanModeController, PLAN_CONFIG)
ctx.llm.registerAdapter(['mock'], adapter)
for (const name of ['read', 'write']) {
ctx.tools.register(defineContentToolFixture({

View File

@@ -2,12 +2,12 @@ import { describe, expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import SessionStore, { Session, SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
import * as PlanModeInvariant from '@deepseek-ai/dsh-plan-mode/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'
import InvariantRegistry from '@deepseek-ai/dsh-invariants'
async function setup(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(InvariantRegistry, { enabled: true })
await ctx.plugin(PlanModeInvariant)
return ctx
}
@@ -67,7 +67,7 @@ describe('plan-mode stream invariants', () => {
session.append('turn/start', { turn: 1 })
session.append('plan/mode', { active: 'plan' as unknown as boolean })
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(InvariantRegistry, { enabled: true })
await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).rejects.toThrow(/expected a boolean/)
})
@@ -79,7 +79,7 @@ describe('plan-mode stream invariants', () => {
session.append('turn/start', { turn: 1 })
session.append('plan/mode', { active: true })
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(InvariantRegistry, { enabled: true })
await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).resolves.toBeUndefined()
})
@@ -88,7 +88,7 @@ describe('plan-mode stream invariants', () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
ctx.sessions.create().append('plan/mode', { active: true })
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(InvariantRegistry, { enabled: true })
await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).resolves.toBeUndefined()
})

View File

@@ -2,16 +2,16 @@ import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { createUserMessage, CallId } from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { RUN_CODE_NAME, defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import ToolRuntime, { RUN_CODE_NAME, defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import { Session, SessionId, type UserMessage } from '@deepseek-ai/dsh-session'
import AgentRegistry, { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
import { createScope } from '@deepseek-ai/dsh-scope'
import UserInteractionService, {
UserInteractionError, type AskUserQuestionRequest,
} from '@deepseek-ai/dsh-user-interaction'
import CommandService from '@deepseek-ai/dsh-commands'
import UserQuestionService, {
UserQuestionError, type AskUserQuestionRequest,
} from '@deepseek-ai/dsh-user-questions'
import CommandRuntime from '@deepseek-ai/dsh-commands'
import { CodeRuntime, type CodeRunRequest, type CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import PlanModeService, { EXIT_PLAN_MODE, foldPlanMode, resolveConfig } from '../src/index.ts'
import PlanModeController, { EXIT_PLAN_MODE, foldPlanMode, resolveConfig } from '../src/index.ts'
import type { PlanModeConfig } from '../src/index.ts'
const TEST_PLAN_SECTION = 'Test plan mode instructions.'
@@ -19,7 +19,7 @@ const PLAN_CONFIG = { section: TEST_PLAN_SECTION } satisfies PlanModeConfig
/**
* Drives the REAL plugin: mounts `dsh-plan-mode` beside real `SystemPrompt` and
* `ToolRegistry` services, with fake Agents carrying real `Session`s and a
* `ToolRuntime` services, with fake Agents carrying real `Session`s and a
* real scoped `agent.ctx` minted through `createScope`.
* Request boundaries are simulated by dispatching the real pre-step waterfall
* and the following `step/start` session event used by the loop.
@@ -68,8 +68,8 @@ function assembleFor(ctx: Context, agent: Agent) {
async function setup(config: PlanModeConfig = PLAN_CONFIG): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(PlanModeService, config)
await ctx.plugin(ToolRuntime)
await ctx.plugin(PlanModeController, config)
return ctx
}
@@ -281,8 +281,8 @@ describe('the boundary flush', () => {
it('removes the pre-step flush when the plugin fiber is disposed', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
const fiber = await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(ToolRuntime)
const fiber = await ctx.plugin(PlanModeController, PLAN_CONFIG)
const agent = await agentWithSession(ctx)
openTurn(agent.session)
ctx.planMode.set(agent, true)
@@ -433,13 +433,13 @@ describe('the soft layer', () => {
// Plan guidance does not filter the registry or later assembly additions.
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(ToolRuntime)
ctx.on('system-prompt/assemble', async (_assembly, _context, next) => {
const final = await next()
final.tools = [...final.tools, { name: 'added-later', description: 'added after next()', parameters: {} }]
return final
})
await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(PlanModeController, PLAN_CONFIG)
registerNamedTools(ctx, ['read'])
const planning = await agentWithSession(ctx, 'planning', { active: true })
expect((await assembleFor(ctx, planning)).tools.map(tool => tool.name))
@@ -459,9 +459,9 @@ describe('the soft layer', () => {
}
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry, { mode: 'code' })
await ctx.plugin(ToolRuntime, { mode: 'code' })
await ctx.plugin(FakeRuntime)
await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(PlanModeController, PLAN_CONFIG)
registerNamedTools(ctx, ['read', 'write'])
const agent = await agentWithSession(ctx, 'agent-1', { active: true })
const assembly = await assembleFor(ctx, agent)
@@ -480,9 +480,9 @@ describe('the soft layer', () => {
}
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry, { mode: 'both' })
await ctx.plugin(ToolRuntime, { mode: 'both' })
await ctx.plugin(FakeRuntime)
await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(PlanModeController, PLAN_CONFIG)
registerNamedTools(ctx, ['read', 'write'])
const agent = await agentWithSession(ctx, 'agent-1', { active: true })
const assembly = await assembleFor(ctx, agent)
@@ -501,9 +501,9 @@ describe('the soft layer', () => {
}
const withPlanMode = new Context()
await withPlanMode.plugin(SystemPrompt)
await withPlanMode.plugin(ToolRegistry, { mode: 'code' })
await withPlanMode.plugin(ToolRuntime, { mode: 'code' })
await withPlanMode.plugin(FakeRuntime)
await withPlanMode.plugin(PlanModeService, PLAN_CONFIG)
await withPlanMode.plugin(PlanModeController, PLAN_CONFIG)
registerNamedTools(withPlanMode, ['read', 'write'])
const agent = await agentWithSession(withPlanMode)
const defaultSdk = (await assembleFor(withPlanMode, agent)).sections.find(section => section.name === 'tools:sdk')?.text ?? ''
@@ -516,7 +516,7 @@ describe('the soft layer', () => {
// with a deployment that does not compose plan mode at all.
const bare = new Context()
await bare.plugin(SystemPrompt)
await bare.plugin(ToolRegistry, { mode: 'code' })
await bare.plugin(ToolRuntime, { mode: 'code' })
await bare.plugin(FakeRuntime)
registerNamedTools(bare, ['read', 'write'])
const bareSdk = (await bare.systemPrompt.assemble({ agent })).sections.find(section => section.name === 'tools:sdk')?.text ?? ''
@@ -553,7 +553,7 @@ describe('/plan', () => {
expect(bare.get('commands')).toBeUndefined()
const ctx = await setup()
await ctx.plugin(CommandService)
await ctx.plugin(CommandRuntime)
// The `ctx.inject` child mounts asynchronously once `commands` resolves.
await new Promise(resolve => setImmediate(resolve))
const plainAgent = await agentWithSession(ctx, 'plain-plan-command')
@@ -595,7 +595,7 @@ describe('/plan', () => {
it('leaves active plan mode, cancels a pending entry, and treats inactive exit as idempotent', async () => {
const ctx = await setup()
await ctx.plugin(CommandService)
await ctx.plugin(CommandRuntime)
await new Promise(resolve => setImmediate(resolve))
const signal = new AbortController().signal
@@ -633,7 +633,7 @@ describe('/plan', () => {
it('idle sessions get the immediate-commit copy on both /plan and /plan off', async () => {
const ctx = await setup()
await ctx.plugin(CommandService)
await ctx.plugin(CommandRuntime)
await new Promise(resolve => setImmediate(resolve))
const signal = new AbortController().signal
const agent = await agentWithSession(ctx, 'idle-plan-command')
@@ -648,9 +648,9 @@ describe('/plan', () => {
it('removes the contributed command when the plan-mode plugin is disposed', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(CommandService)
const fiber = await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(ToolRuntime)
await ctx.plugin(CommandRuntime)
const fiber = await ctx.plugin(PlanModeController, PLAN_CONFIG)
await new Promise(resolve => setImmediate(resolve))
const agent = await agentWithSession(ctx)
expect(ctx.commands.list(agent).map(command => command.name)).toEqual(['plan'])
@@ -665,10 +665,10 @@ describe('exit_plan_mode', () => {
async function setupWithReview(answer?: { selected: string[]; custom?: string }) {
const ctx = await setup()
await ctx.plugin(AgentRegistry)
await ctx.plugin(UserInteractionService)
await ctx.plugin(UserQuestionService)
const asked: AskUserQuestionRequest[] = []
if (answer !== undefined) {
ctx.userInteraction.registerProvider({
ctx.userQuestions.registerProvider({
ask: (request) => {
asked.push(request)
return Promise.resolve({ answers: [{ id: 'plan-review', ...answer }] })
@@ -725,12 +725,12 @@ describe('exit_plan_mode', () => {
expect(foldPlanMode(agent.session.events)).toBe(true)
})
it('degrades to the manual exit when no user-interaction seam is composed', async () => {
it('degrades to the manual exit when no user-questions seam is composed', async () => {
const ctx = await setup()
const agent = await agentWithSession(ctx, 'agent-1', { active: true })
const result = await callExit(ctx, agent)
expect(result.isError).toBe(true)
expect(result.content).toEqual([{ type: 'text', text: 'Error: no user-interaction channel is available to review the plan; ask the user to switch the session mode instead' }])
expect(result.content).toEqual([{ type: 'text', text: 'Error: no user-questions channel is available to review the plan; ask the user to switch the session mode instead' }])
expect(foldPlanMode(agent.session.events)).toBe(true)
})
@@ -738,16 +738,16 @@ describe('exit_plan_mode', () => {
const { ctx, agent } = await setupWithReview()
const result = await callExit(ctx, agent)
expect(result.isError).toBe(true)
expect(result.content).toEqual([{ type: 'text', text: 'Error: no user-interaction provider is registered' }])
expect(result.content).toEqual([{ type: 'text', text: 'Error: no user-questions provider is registered' }])
expect(foldPlanMode(agent.session.events)).toBe(true)
})
it('rejects review from a runtime-owned agent with consumer-neutral guidance', async () => {
const ctx = await setup()
await ctx.plugin(AgentRegistry)
await ctx.plugin(UserInteractionService)
await ctx.plugin(UserQuestionService)
const ask = vi.fn(async () => ({ answers: [{ id: 'plan-review', selected: ['Approve'] }] }))
ctx.userInteraction.registerProvider({ ask })
ctx.userQuestions.registerProvider({ ask })
const root = await agentWithSession(ctx, 'review-root')
const child = await agentWithSession(ctx, 'review-child', { active: true, owner: root })
@@ -794,13 +794,13 @@ describe('exit_plan_mode', () => {
}
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry, { mode: 'code' })
await ctx.plugin(ToolRuntime, { mode: 'code' })
await ctx.plugin(ExitRuntime)
await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(PlanModeController, PLAN_CONFIG)
await ctx.plugin(AgentRegistry)
await ctx.plugin(UserInteractionService)
await ctx.plugin(UserQuestionService)
const asked: AskUserQuestionRequest[] = []
ctx.userInteraction.registerProvider({
ctx.userQuestions.registerProvider({
ask: (request) => {
asked.push(request)
return Promise.resolve({ answers: [{ id: 'plan-review', selected: ['Approve'] }] })
@@ -899,7 +899,7 @@ describe('exit_plan_mode', () => {
it('treats duplicate review answer items as non-consent', async () => {
const { ctx, agent } = await setupWithReview()
ctx.userInteraction.registerProvider({
ctx.userQuestions.registerProvider({
ask: () => Promise.resolve({ answers: [
{ id: 'plan-review', selected: ['Approve'] },
{ id: 'plan-review', selected: ['Keep planning'] },
@@ -913,7 +913,7 @@ describe('exit_plan_mode', () => {
it('a missing answer item reads as keep-planning', async () => {
const { ctx, agent } = await setupWithReview()
ctx.userInteraction.registerProvider({ ask: () => Promise.resolve({ answers: [] }) })
ctx.userQuestions.registerProvider({ ask: () => Promise.resolve({ answers: [] }) })
const result = await callExit(ctx, agent)
expect(result.isError).toBe(true)
expect(result.content).toEqual([{ type: 'text', text: 'Error: The user chose to keep planning; revise the plan and present it again.' }])
@@ -931,8 +931,8 @@ describe('exit_plan_mode', () => {
it('reads a dismissed review as the user taking the turn back, not as a failure', async () => {
const { ctx, agent } = await setupWithReview()
ctx.userInteraction.registerProvider({
ask: () => Promise.reject(new UserInteractionError(
ctx.userQuestions.registerProvider({
ask: () => Promise.reject(new UserQuestionError(
'the user cancelled ask_user_question', 'ASK_CANCELLED')),
})
const result = await callExit(ctx, agent)
@@ -943,8 +943,8 @@ describe('exit_plan_mode', () => {
it('leaves every other review failure its own message', async () => {
const { ctx, agent } = await setupWithReview()
ctx.userInteraction.registerProvider({
ask: () => Promise.reject(new UserInteractionError(
ctx.userQuestions.registerProvider({
ask: () => Promise.reject(new UserQuestionError(
'ask_user_question was aborted before the user answered', 'ASK_ABORTED')),
})
const result = await callExit(ctx, agent)
@@ -970,12 +970,12 @@ describe('exit_plan_mode', () => {
it('fails the call when the plugin is disposed while the review awaits (no phantom exit)', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
const fiber = await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(ToolRuntime)
const fiber = await ctx.plugin(PlanModeController, PLAN_CONFIG)
await ctx.plugin(AgentRegistry)
await ctx.plugin(UserInteractionService)
await ctx.plugin(UserQuestionService)
let answer!: (value: { answers: { id: string; selected: string[] }[] }) => void
ctx.userInteraction.registerProvider({
ctx.userQuestions.registerProvider({
ask: () => new Promise((resolve) => { answer = resolve }),
})
const agent = await agentWithSession(ctx, 'agent-1', { active: true })
@@ -994,7 +994,7 @@ describe('exit_plan_mode', () => {
it('a throwing provider surfaces as the corrective isError and the mode stays plan', async () => {
const { ctx, agent } = await setupWithReview()
ctx.userInteraction.registerProvider({ ask: () => { throw new Error('review aborted') } })
ctx.userQuestions.registerProvider({ ask: () => { throw new Error('review aborted') } })
const result = await callExit(ctx, agent)
expect(result.isError).toBe(true)
expect(result.content).toEqual([{ type: 'text', text: 'Error: review aborted' }])
@@ -1034,12 +1034,12 @@ describe('HMR disposal', () => {
it('unregisters the service, listeners, prompt section, and stable exit tool with the plugin fiber', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
const fiber = await ctx.plugin(PlanModeService, PLAN_CONFIG)
await ctx.plugin(ToolRuntime)
const fiber = await ctx.plugin(PlanModeController, PLAN_CONFIG)
const agent = await agentWithSession(ctx, 'disposed-recovery')
openTurn(agent.session)
ctx.planMode.set(agent, true)
expect(ctx.get('planMode')).toBeInstanceOf(PlanModeService)
expect(ctx.get('planMode')).toBeInstanceOf(PlanModeController)
expect(ctx.tools.get(EXIT_PLAN_MODE)).toBeDefined()
expect((await ctx.systemPrompt.assemble()).sections.map(section => section.name)).toContain('plan:policy')

View File

@@ -16,11 +16,11 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import SessionStore from '@deepseek-ai/dsh-session'
import type { Session } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import ToolRuntime from '@deepseek-ai/dsh-tools'
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
import UserQuestionService from '@deepseek-ai/dsh-user-questions'
import { CommandId } from '@deepseek-ai/dsh-commands/brand'
import PlanModeService from '@deepseek-ai/dsh-plan-mode'
import PlanModeController from '@deepseek-ai/dsh-plan-mode'
interface Bench {
ctx: Context
@@ -32,11 +32,11 @@ async function harness(withPlanMode: boolean): Promise<Bench> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt, { persona: '' })
await ctx.plugin(ToolRegistry)
await ctx.plugin(UserInteractionService)
await ctx.plugin(ToolRuntime)
await ctx.plugin(UserQuestionService)
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
if (withPlanMode) await ctx.plugin(PlanModeService, { section: 'plan policy' })
if (withPlanMode) await ctx.plugin(PlanModeController, { section: 'plan policy' })
const session = ctx.sessions.create()
ctx.agents.register({ id: session.id, session, status: 'idle', ctx } as Agent)
return {
@@ -115,7 +115,7 @@ describe('plan projection unit', () => {
it('drops the key when the plan-mode fiber unloads (HMR safety)', async () => {
const bench = await harness(false)
const fiber = await bench.ctx.plugin(PlanModeService, { section: 'plan policy' })
const fiber = await bench.ctx.plugin(PlanModeController, { section: 'plan policy' })
expect(bench.values().plan).toEqual({ active: false, pending: false })
await fiber.dispose()
expect('plan' in bench.values()).toBe(false)

View File

@@ -27,7 +27,7 @@
"path": "../../core/system-prompt"
},
{
"path": "../../interaction/user-interaction"
"path": "../../interaction/user-questions"
},
{
"path": "../../interaction/commands"
@@ -36,7 +36,7 @@
"path": "../../session/session-projection"
},
{
"path": "../../support/invariants"
"path": "../../runtime-diagnostics/invariants"
}
]
}