Merge branch 'codex/commands' into codex/goal-commands
# Conflicts: # docs/architecture.md # docs/config-catalog.md # docs/cookbook/extension-cookbook.i18n.yaml # docs/module-graph.md # packages/examples/README.md # packages/examples/agent-spine-demo/README.md # packages/examples/stdio-demo/README.md # packages/examples/stdio-demo/src/index.ts # packages/examples/stdio-demo/tests/stdio-agent.spec.ts
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# @deepseek-ai/dsh-tui-demo
|
||||
|
||||
The full-screen terminal app: a Cordis plugin that composes [`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md), persisted same-session goals, the human-command registry and `/goal` producer, JSONL persistence, keyboard-backed user interaction, a pre-created `main` agent, and [`@deepseek-ai/dsh-tui`](../../ui/tui/README.md). Its `bin` boots a leaf `cordis.yml`.
|
||||
|
||||
Use [`@deepseek-ai/dsh-cli-demo`](../cli-demo/README.md) for pipes, scripts, and other non-interactive runs. This package requires a TTY pair and has no line-oriented fallback.
|
||||
|
||||
## What it bakes in
|
||||
|
||||
| Plugin | Why it is here |
|
||||
|---|---|
|
||||
| `@deepseek-ai/dsh-agent-spine-demo` | Shared services, model-facing tools, and one configured `main` agent |
|
||||
| `@deepseek-ai/dsh-commands` | Human-only discovery and dispatch consumed by the TUI and command plugins |
|
||||
| `@deepseek-ai/dsh-command-goal` | Direct `/goal` status and mutation over the spine's persisted-goal stack |
|
||||
| `@deepseek-ai/dsh-session-persistence-jsonl` | Durable session log under `persistenceRoot` |
|
||||
| `@deepseek-ai/dsh-user-interaction` | Provider-neutral human question service |
|
||||
| `@deepseek-ai/dsh-tui` | Full-screen transcript, editor, tool cards, plan, and question overlays |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | Model-facing `ask_user_question` tool |
|
||||
|
||||
Swappable LLM, bash, filesystem, and other capability providers remain in the leaf config. `@cordisjs/plugin-hmr` also remains a leaf-only development entry because it requires Loader internals.
|
||||
|
||||
## Config
|
||||
|
||||
| Key | Default | Routed to |
|
||||
|---|---|---|
|
||||
| `provider` | required | Configured `main` agent provider |
|
||||
| `model` | required | Configured `main` agent model |
|
||||
| `maxParallelToolCalls` | agent-loop default | Bundled loop concurrency cap |
|
||||
| `persona` | — | System-prompt persona template |
|
||||
| `toolOrder` | lexicographic | Explicit model-facing tool order |
|
||||
| `tools` | owner default | Tool presentation mode |
|
||||
| `dshHome` | owner default | Harness home used by bash and skills |
|
||||
| `skills` | owner defaults | Skill registry, local provider, and tool config |
|
||||
| `toolBash` | owner defaults | Model-facing bash tool config |
|
||||
| `toolTasks` | owner defaults | Background-task control-tool config, or `false` |
|
||||
| `goals` | owner defaults | Persisted goal-domain and model-tool config; `false` removes the goal stack and `/goal` producer |
|
||||
| `workspaceContext` | required | Workspace-instruction config, or `false` |
|
||||
| `persistenceRoot` | `./.sessions` | JSONL persistence root |
|
||||
| `persistenceCompression` | `'zstd'` | JSONL artifact encoding (`'zstd'` or raw `'none'`) |
|
||||
| `welcome` | `ready.` | TUI subtitle |
|
||||
| `ui` | owner defaults | TUI presentation settings such as reasoning, color, and card height |
|
||||
| `resumeSessionId` | — | Exact persisted session to resume |
|
||||
|
||||
Fresh runs mint a `main-session-<uuid>` session id and pass it to both the TUI and configured agent. Resumed runs bind both components to `resumeSessionId`. The TUI mounts before the spine so it can render a matching config-start failure instead of leaving a blank terminal.
|
||||
|
||||
## The bin
|
||||
|
||||
`dsh-tui-demo [path-to-cordis.yml]` defaults to `./cordis.yml`, loads the optional cwd `.env`, boots the Cordis Loader, and waits for the full plugin tree. Bare package specifiers require `node --expose-internals` or the Loader's optional native fallback; the repository scripts use `--expose-internals`.
|
||||
|
||||
## Example leaf
|
||||
|
||||
```yaml
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
welcome: 'Coding agent ready.'
|
||||
ui:
|
||||
showReasoning: true
|
||||
```
|
||||
|
||||
## Model Experience
|
||||
|
||||
### Interactive terminal turn
|
||||
|
||||
#### What the model sees
|
||||
|
||||
Each non-empty non-command editor submission becomes a user message; a submission during a running turn becomes steering. Slash-command input and output remain human-only, while accepted `/goal` mutations append domain-owned model-visible state. The shared spine contributes the configured persona, workspace instructions, skill catalog, goal controls, and visible tool schemas. TUI rendering itself is not model-visible.
|
||||
|
||||
#### Token effect
|
||||
|
||||
User, assistant, and tool history grows under the normal session and compaction rules. Headers, cards, plans, Markdown styling, and keybindings add no tokens.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
Append-only while the composed prompt, schemas, route, and retained history prefix remain stable. Composition changes and compaction can invalidate reuse from the first changed token.
|
||||
|
||||
### Human-question answer
|
||||
|
||||
#### What the model sees
|
||||
|
||||
`ask_user_question` retains the tool call and the compact answer or stable interruption error defined by `dsh-tool-ask-user`. The question overlay is terminal-only.
|
||||
|
||||
#### Token effect
|
||||
|
||||
Only the completed or failed tool result adds retained tokens.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
Append-only; the answer follows the reusable request prefix.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **TTY-only** — stdin and stdout must both be terminals; automation uses `dsh-cli-demo`.
|
||||
- **One configured terminal session** — the transcript and editor bind to one exact session id.
|
||||
- **The app cluster is fixed** — JSONL persistence and ask-user tooling are baked in; different policy requires another composition.
|
||||
- **Approval is separate** — this app answers `ctx.userInteraction`, not `ctx.approval`; permission prompts require an approval service and answerer.
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-tui-demo",
|
||||
"description": "Full-screen terminal app: agent spine + persisted goals + human commands + JSONL persistence + pi-tui front door + pre-created main agent",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"bin": {
|
||||
"dsh-tui-demo": "lib/bin.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./bin": {
|
||||
"types": "./lib/types/bin.d.ts",
|
||||
"default": "./lib/bin.js"
|
||||
},
|
||||
"./src/*": "./src/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/bin.js",
|
||||
"lib/types/**/*.d.ts",
|
||||
"lib/types/**/*.d.ts.map",
|
||||
"src"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@cordisjs/plugin-include": "^1.0.4",
|
||||
"@cordisjs/plugin-loader": "^1.0.0-rc.5",
|
||||
"@deepseek-ai/dsh-app-boot": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-loop": "^0.0.1",
|
||||
"@deepseek-ai/dsh-commands": "^0.0.1",
|
||||
"@deepseek-ai/dsh-command-goal": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "^0.0.1",
|
||||
"@deepseek-ai/dsh-workspace-context": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tui": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tool-ask-user": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.0.1",
|
||||
"@deepseek-ai/dsh-user-interaction": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7",
|
||||
"schemastery": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cordisjs/plugin-include": "workspace:^",
|
||||
"@cordisjs/plugin-loader": "workspace:^",
|
||||
"@deepseek-ai/dsh-app-boot": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-commands": "workspace:^",
|
||||
"@deepseek-ai/dsh-command-goal": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "workspace:^",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
||||
"@deepseek-ai/dsh-workspace-context": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
|
||||
"@deepseek-ai/dsh-tui": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-ask-user": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"@deepseek-ai/dsh-user-interaction": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7",
|
||||
"schemastery": "^3.17.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Boot a TUI app from a leaf `cordis.yml`; usage is `dsh-tui-demo [config]`, defaulting to the
|
||||
* cwd file. Shared `.env` loading, fail-loud Loader guards, and settled-tree boot live in
|
||||
* dsh-app-boot. The tui-agent and cordis-agent demos invoke this bin with their own leaf configs.
|
||||
* @module @deepseek-ai/dsh-tui-demo/bin
|
||||
*/
|
||||
|
||||
import { boot, installFailLoud, loadEnv, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
|
||||
|
||||
const NAME = 'dsh-tui-demo'
|
||||
|
||||
/* v8 ignore start -- thin self-executing composition over the unit-tested
|
||||
dsh-app-boot helpers; exercised end-to-end by the keyless Loader-path and
|
||||
built-bin smokes */
|
||||
installFailLoud(NAME)
|
||||
loadEnv(NAME)
|
||||
await boot(NAME, resolveConfigPath(process.argv[2] ?? './cordis.yml', undefined))
|
||||
/* v8 ignore stop */
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Full-screen terminal app: the default agent spine ({@link @deepseek-ai/dsh-agent-spine-demo})
|
||||
* plus persisted goals, human commands, JSONL persistence, keyboard-backed
|
||||
* user interaction, and one pre-created agent whose exact session identity the
|
||||
* TUI drives. Swappable adapters, executors, optional tools, and HMR stay in the leaf. This Loader plugin
|
||||
* intentionally exposes named exports only; a default export would hide its
|
||||
* `Config` schema (see docs/postmortem/0001).
|
||||
* @module @deepseek-ai/dsh-tui-demo
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import z from 'schemastery'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
|
||||
import CommandService from '@deepseek-ai/dsh-commands'
|
||||
import * as commandGoal from '@deepseek-ai/dsh-command-goal'
|
||||
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
|
||||
import SessionPersistenceJsonl, {
|
||||
JsonlCompressionSchema,
|
||||
type JsonlCompression,
|
||||
} from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
import * as toolAskUser from '@deepseek-ai/dsh-tool-ask-user'
|
||||
import * as uiTui from '@deepseek-ai/dsh-tui'
|
||||
|
||||
export const name = 'tui-demo'
|
||||
const DEFAULT_PERSISTENCE_ROOT = './.sessions'
|
||||
const DEFAULT_WELCOME = 'ready.'
|
||||
|
||||
/** App config routed to the spine, TUI, configured agent, and JSONL backend. */
|
||||
export interface Config {
|
||||
/** Provider route for the `main` agent. */
|
||||
provider: string
|
||||
/** Model name for the `main` agent; a matching adapter must be registered. */
|
||||
model: string
|
||||
/** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */
|
||||
maxParallelToolCalls?: number
|
||||
/** Deployment persona forwarded to the system-prompt plugin. */
|
||||
persona?: string
|
||||
/** Explicit model-facing tool order forwarded to the system-prompt plugin. */
|
||||
toolOrder?: string[]
|
||||
/** Tool-registry presentation config forwarded through agent-spine-demo. */
|
||||
tools?: ToolsConfig
|
||||
/** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
|
||||
dshHome?: string
|
||||
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
|
||||
persistenceRoot?: string
|
||||
/** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
|
||||
persistenceCompression?: JsonlCompression
|
||||
/** TUI subtitle rendered on start. Defaults to `ready.`. */
|
||||
welcome?: string
|
||||
/** Full-screen TUI presentation settings. */
|
||||
ui?: uiTui.TuiConfig
|
||||
/** Skill registry, local-provider, and model-facing consumer config. */
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-spine-demo. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task controls forwarded through agent-spine-demo; set false to omit them. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and command. */
|
||||
goals?: agentCore.GoalConfig | false
|
||||
/** Persisted session id to resume instead of creating a fresh session. */
|
||||
resumeSessionId?: string
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
workspaceContext: agentCore.Config['workspaceContext']
|
||||
}
|
||||
|
||||
// Each front door keeps a complete Loader schema so its deployment contract is
|
||||
// readable without a cross-package config facade.
|
||||
/* jscpd:ignore-start */
|
||||
export const Config: z<Config> = z.object({
|
||||
provider: z.string().required(),
|
||||
model: z.string().required(),
|
||||
maxParallelToolCalls: z.number().step(1).min(1),
|
||||
persona: z.string(),
|
||||
// Absent means lexicographic order; schemastery's native array default is [].
|
||||
toolOrder: z.array(z.string()).default(undefined as unknown as string[]),
|
||||
tools: ToolRegistry.Config,
|
||||
dshHome: z.string(),
|
||||
persistenceRoot: z.string().default(DEFAULT_PERSISTENCE_ROOT),
|
||||
persistenceCompression: JsonlCompressionSchema,
|
||||
welcome: z.string().default(DEFAULT_WELCOME),
|
||||
ui: uiTui.TuiConfigSchema,
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
toolTasks: z.union([z.const(false), agentCore.ToolTasksConfigSchema]),
|
||||
goals: z.union([z.const(false), agentCore.GoalConfigSchema]),
|
||||
resumeSessionId: z.string(),
|
||||
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
|
||||
})
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
/**
|
||||
* Compose the spine, TUI, JSONL persistence, and user-question tool around one
|
||||
* exact fresh or resumed session identity. The TUI subscribes to startup
|
||||
* failures before the spine creates the agent.
|
||||
* @param ctx - context receiving the app's child plugins.
|
||||
* @param config - validated app configuration.
|
||||
*/
|
||||
export function composeTuiApp(ctx: Context, config: Config): void {
|
||||
const resumeSessionId = config.resumeSessionId === '' ? undefined : config.resumeSessionId
|
||||
const sessionId = SessionId(resumeSessionId ?? `main-session-${randomUUID()}`)
|
||||
const goals = config.goals ?? {}
|
||||
ctx.plugin(CommandService)
|
||||
if (goals !== false) ctx.plugin(commandGoal)
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT,
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
})
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(uiTui, {
|
||||
...config.ui,
|
||||
welcome: config.welcome ?? DEFAULT_WELCOME,
|
||||
sessionId,
|
||||
})
|
||||
ctx.plugin(agentCore, {
|
||||
...agentCore.pickSpineConfig(config),
|
||||
goals,
|
||||
agents: [{
|
||||
id: SessionId('main'),
|
||||
provider: config.provider,
|
||||
model: config.model,
|
||||
cwd: process.cwd(),
|
||||
...resumeSessionId === undefined ? { sessionId } : { resumeSessionId: sessionId },
|
||||
}],
|
||||
})
|
||||
ctx.plugin(toolAskUser)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the configured full-screen terminal app.
|
||||
* @param ctx - context receiving the app's child plugins.
|
||||
* @param config - validated app configuration.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
composeTuiApp(ctx, config)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt'
|
||||
import * as tuiAgent from '../src/index.ts'
|
||||
|
||||
interface PluginCall {
|
||||
readonly name: string
|
||||
readonly config: unknown
|
||||
}
|
||||
|
||||
function recordingContext(): { readonly ctx: Context; readonly calls: PluginCall[] } {
|
||||
const calls: PluginCall[] = []
|
||||
const ctx = {
|
||||
plugin(plugin: { name?: string }, config?: unknown) {
|
||||
calls.push({ name: plugin.name ?? '', config })
|
||||
},
|
||||
} as unknown as Context
|
||||
return { ctx, calls }
|
||||
}
|
||||
|
||||
describe('dsh-tui-demo app', () => {
|
||||
it('composes the TUI cluster around one fresh exact session identity', () => {
|
||||
const { ctx, calls } = recordingContext()
|
||||
tuiAgent.composeTuiApp(ctx, {
|
||||
provider: 'mock',
|
||||
model: 'mock-model',
|
||||
maxParallelToolCalls: 3,
|
||||
persona: 'test persona',
|
||||
toolOrder: ['zulu', TOOL_ORDER_REST],
|
||||
tools: { mode: 'code' },
|
||||
dshHome: '/tmp/dsh-home',
|
||||
persistenceRoot: '/tmp/tui-sessions',
|
||||
persistenceCompression: 'none',
|
||||
welcome: 'TUI ready',
|
||||
ui: { color: false, maxToolOutputLines: 3 },
|
||||
skills: { tool: { catalogDescriptionMaxLength: 8 } },
|
||||
toolBash: { enableRunInBackground: false },
|
||||
toolTasks: { waitTimeoutMs: 7, maxWaitTimeoutMs: 11 },
|
||||
workspaceContext: false,
|
||||
})
|
||||
|
||||
expect(calls.map(call => call.name)).toEqual([
|
||||
'CommandService',
|
||||
'command-goal',
|
||||
'SessionPersistenceJsonl',
|
||||
'UserInteractionService',
|
||||
'ui-tui',
|
||||
'agent-spine-demo',
|
||||
'tool-ask-user',
|
||||
])
|
||||
expect(calls[0]?.config).toBeUndefined()
|
||||
expect(calls[2]?.config).toEqual({ root: '/tmp/tui-sessions', compression: 'none' })
|
||||
const tuiConfig = calls[4]?.config as { sessionId: string }
|
||||
expect(tuiConfig).toMatchObject({ welcome: 'TUI ready', color: false, maxToolOutputLines: 3 })
|
||||
expect(tuiConfig.sessionId).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
const spineConfig = calls[5]?.config as {
|
||||
readonly agents: Array<Record<string, unknown>>
|
||||
readonly goals: Record<string, never>
|
||||
readonly maxParallelToolCalls: number
|
||||
readonly persona: string
|
||||
readonly toolOrder: string[]
|
||||
readonly tools: { mode: string }
|
||||
}
|
||||
expect(spineConfig).toMatchObject({
|
||||
maxParallelToolCalls: 3,
|
||||
persona: 'test persona',
|
||||
toolOrder: ['zulu', TOOL_ORDER_REST],
|
||||
tools: { mode: 'code' },
|
||||
goals: {},
|
||||
})
|
||||
expect(spineConfig.agents[0]).toMatchObject({
|
||||
id: 'main',
|
||||
provider: 'mock',
|
||||
model: 'mock-model',
|
||||
cwd: process.cwd(),
|
||||
sessionId: tuiConfig.sessionId,
|
||||
})
|
||||
})
|
||||
|
||||
it('resumes the configured session and applies runtime defaults', () => {
|
||||
const { ctx, calls } = recordingContext()
|
||||
tuiAgent.composeTuiApp(ctx, {
|
||||
provider: 'mock',
|
||||
model: 'mock-model',
|
||||
resumeSessionId: 'persisted-session',
|
||||
workspaceContext: false,
|
||||
})
|
||||
|
||||
expect(calls[2]?.config).toEqual({ root: './.sessions' })
|
||||
expect(calls[4]?.config).toEqual({ welcome: 'ready.', sessionId: 'persisted-session' })
|
||||
expect((calls[5]?.config as { agents: Array<Record<string, unknown>> }).agents[0]).toMatchObject({
|
||||
id: 'main',
|
||||
resumeSessionId: 'persisted-session',
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes an empty resume id and routes apply through the same composition', () => {
|
||||
const { ctx, calls } = recordingContext()
|
||||
tuiAgent.apply(ctx, {
|
||||
provider: 'mock',
|
||||
model: 'mock-model',
|
||||
resumeSessionId: '',
|
||||
goals: false,
|
||||
workspaceContext: false,
|
||||
})
|
||||
|
||||
const tuiConfig = calls[3]?.config as { sessionId: string }
|
||||
expect(tuiConfig.sessionId).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
expect((calls[4]?.config as { agents: Array<Record<string, unknown>> }).agents[0])
|
||||
.toMatchObject({ sessionId: tuiConfig.sessionId })
|
||||
expect(calls.map(call => call.name)).not.toContain('command-goal')
|
||||
expect(calls[4]?.config).toMatchObject({ goals: false })
|
||||
})
|
||||
|
||||
it('has the namespace-plugin export shape so the Loader keeps its schema', () => {
|
||||
expect(tuiAgent.name).toBe('tui-demo')
|
||||
expect(tuiAgent.Config).toBeDefined()
|
||||
expect('default' in tuiAgent).toBe(false)
|
||||
expect(typeof tuiAgent.apply).toBe('function')
|
||||
|
||||
const loader = Object.create(Loader.prototype) as Loader
|
||||
const unwrapped = loader.unwrapExports(tuiAgent) as Record<string, unknown>
|
||||
expect(unwrapped).toBe(tuiAgent)
|
||||
expect(unwrapped.name).toBe('tui-demo')
|
||||
expect(unwrapped.Config).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/schemastery"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/loader"
|
||||
},
|
||||
{
|
||||
"path": "../../ui/app-boot"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent"
|
||||
},
|
||||
{
|
||||
"path": "../../core/session"
|
||||
},
|
||||
{
|
||||
"path": "../../ui/commands"
|
||||
},
|
||||
{
|
||||
"path": "../../goal/command-goal"
|
||||
},
|
||||
{
|
||||
"path": "../agent-spine-demo"
|
||||
},
|
||||
{
|
||||
"path": "../../context/workspace-context"
|
||||
},
|
||||
{
|
||||
"path": "../../ui/user-interaction"
|
||||
},
|
||||
{
|
||||
"path": "../../ui/tui"
|
||||
},
|
||||
{
|
||||
"path": "../../ui/tool-ask-user"
|
||||
},
|
||||
{
|
||||
"path": "../../session-persistence/session-persistence-jsonl"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'tsdown'
|
||||
|
||||
/**
|
||||
* tui-demo ships two entries: the plugin (`index`) and the CLI `bin`
|
||||
* (`bin`), the latter referenced by package.json `bin`/`exports["./bin"]`.
|
||||
* The root tsdown builds only `lib/types/index.js`, so this override adds
|
||||
* `lib/types/bin.js`. Declarations come from `tsc -b` (dts: false),
|
||||
* matching every package.
|
||||
*/
|
||||
export default defineConfig({
|
||||
entry: ['lib/types/index.js', 'lib/types/bin.js'],
|
||||
outDir: 'lib',
|
||||
format: ['esm'],
|
||||
platform: 'node',
|
||||
target: 'es2024',
|
||||
fixedExtension: false,
|
||||
dts: false,
|
||||
clean: false,
|
||||
})
|
||||
Reference in New Issue
Block a user