Master unifies every live agent with its exact SessionId and moves declarative startup failures to agent-loop/config-start-failed. Keeping the branch’s AgentId label binding would let the TUI target the wrong lifecycle after reload and would miss asynchronous resume failures. Resolve that contract migration by giving the selected terminal front door the same generated or resumed SessionId as agent-core, mounting the front door first, and entering fullscreen only after the matching root appears. Refresh the source-derived catalogs and keyless terminal goldens so Code Mode, workflow, Cordis-tool, and transient UI scenarios all exercise the merged identity model.
132 lines
4.0 KiB
TypeScript
132 lines
4.0 KiB
TypeScript
import { Context } from 'cordis'
|
|
import type { Terminal } from '@earendil-works/pi-tui'
|
|
import AgentRegistry, { type Agent, type AgentStatus } from '@deepseek-ai/dsh-agent'
|
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
import SessionStore, { SessionId, type Session } from '@deepseek-ai/dsh-session'
|
|
import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
|
|
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
|
import { createTuiChat, type Config } from '../src/index.ts'
|
|
|
|
interface FakeAgent extends Agent {
|
|
status: AgentStatus
|
|
sent: ContentBlock[][]
|
|
steered: ContentBlock[][]
|
|
cancelled: string[]
|
|
}
|
|
|
|
export interface TuiHarnessOptions {
|
|
status?: AgentStatus
|
|
config?: Config
|
|
tools?: Record<string, ToolDefinition>
|
|
configureContext?: (ctx: Context) => Promise<void>
|
|
beforeMount?: (session: Session) => void
|
|
cwd?: string | null
|
|
}
|
|
|
|
export interface TuiHarness<TerminalType extends Terminal, Exit extends (code: number) => void> {
|
|
ctx: Context
|
|
session: Session
|
|
agent: FakeAgent
|
|
terminal: TerminalType
|
|
exit: Exit
|
|
controller: ReturnType<typeof createTuiChat>
|
|
}
|
|
|
|
/**
|
|
* Compose the production TUI around an in-memory session and controllable agent.
|
|
* @param terminal - Terminal boundary driven by the test.
|
|
* @param exit - Process-exit observer.
|
|
* @param options - Initial session, agent, tool, and TUI configuration.
|
|
* @returns The mounted TUI and every boundary the test may drive or inspect.
|
|
*/
|
|
export async function createTuiTestHarness<TerminalType extends Terminal, Exit extends (code: number) => void>(
|
|
terminal: TerminalType,
|
|
exit: Exit,
|
|
options: TuiHarnessOptions = {},
|
|
): Promise<TuiHarness<TerminalType, Exit>> {
|
|
const ctx = new Context()
|
|
await ctx.plugin(SessionStore)
|
|
await ctx.plugin(AgentRegistry)
|
|
await ctx.plugin(UserInteractionService)
|
|
if (options.configureContext === undefined) {
|
|
const tools = options.tools ?? {}
|
|
ctx.provide('tools', {
|
|
get(name: string) {
|
|
return tools[name]
|
|
},
|
|
} as never)
|
|
} else {
|
|
await options.configureContext(ctx)
|
|
}
|
|
const sessionId = SessionId('main-session')
|
|
const session = ctx.sessions.create(
|
|
sessionId,
|
|
options.cwd === null ? undefined : { meta: { cwd: options.cwd ?? '/workspace' } },
|
|
)
|
|
options.beforeMount?.(session)
|
|
const sent: ContentBlock[][] = []
|
|
const steered: ContentBlock[][] = []
|
|
const cancelled: string[] = []
|
|
const agent: FakeAgent = {
|
|
id: sessionId,
|
|
options: { model: 'deepseek-v4-flash' },
|
|
session,
|
|
status: options.status ?? 'idle',
|
|
ctx,
|
|
sent,
|
|
steered,
|
|
cancelled,
|
|
send(content) {
|
|
sent.push(content)
|
|
},
|
|
steer(content) {
|
|
steered.push(content)
|
|
},
|
|
inject() {},
|
|
cancel(reason) {
|
|
cancelled.push(reason ?? '')
|
|
},
|
|
whenIdle() {
|
|
return Promise.resolve()
|
|
},
|
|
}
|
|
ctx.agents.register(agent)
|
|
const controller = createTuiChat(ctx, Object.assign({
|
|
welcome: 'Coding agent ready.',
|
|
sessionId,
|
|
color: false,
|
|
}, options.config), { terminal, exit })
|
|
return { ctx, session, agent, terminal, exit, controller }
|
|
}
|
|
|
|
/** Dispose the mounted TUI before its owning Cordis context. */
|
|
export async function disposeTuiTestHarness(
|
|
setup: Pick<TuiHarness<Terminal, (code: number) => void>, 'controller' | 'ctx'>,
|
|
): Promise<void> {
|
|
await setup.controller.dispose()
|
|
await setup.ctx.fiber.dispose()
|
|
}
|
|
|
|
/** Append a production-shaped user message to the active session surface. */
|
|
export function appendUser(session: Session, text: string): void {
|
|
session.append('user/message', {
|
|
content: [{ type: 'text', text }],
|
|
source: { kind: 'user' },
|
|
}, { surfaceOp: 'append' })
|
|
}
|
|
|
|
/** Append a production-shaped assistant message to the active session surface. */
|
|
export function appendAssistant(
|
|
session: Session,
|
|
content: ContentBlock[],
|
|
usage?: { inputTokens: number; outputTokens: number },
|
|
): void {
|
|
session.append('assistant/message', {
|
|
turn: 1,
|
|
step: 0,
|
|
provenance: { provider: 'mock', model: 'deepseek-v4-flash' },
|
|
content,
|
|
...usage === undefined ? {} : { usage },
|
|
}, { surfaceOp: 'append' })
|
|
}
|