feat(feedback): add a /feedback command recorded through the command plane
Register a global `/feedback` command so a user can record a remark about the session without spending a model turn. `/feedback <text>` acknowledges; empty or whitespace-only input returns a usage error. The plugin appends no session event of its own. `dsh-commands` already writes a `command/run` / `command/done` pair for every dispatched command, carrying the verbatim text and the settled outcome, and both records are log-only and non-surface. The feedback is therefore durably in the session log and invisible to the model without this package touching the log format. Text is never parsed, so `/feedback /plan felt slow` records that literal content. Nothing consumes the records; capture is deliberately inert. New group `packages/feedback/` — no existing group owns feedback capture. Its row raises the packages/README.md word ceiling by 10, which had no headroom; one redundant sentence there was removed to offset most of the cost.
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
"@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-command-feedback": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "^0.0.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
@@ -55,6 +56,7 @@
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-commands": "workspace:^",
|
||||
"@deepseek-ai/dsh-command-goal": "workspace:^",
|
||||
"@deepseek-ai/dsh-command-feedback": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Full-screen terminal app: the default agent spine ({@link @deepseek-ai/dsh-agent-spine-demo})
|
||||
* plus persisted goals, human commands, JSONL persistence, keyboard-backed
|
||||
* plus persisted goals, human commands including `/feedback`, 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
|
||||
@@ -16,6 +16,7 @@ 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 commandFeedback from '@deepseek-ai/dsh-command-feedback'
|
||||
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
|
||||
import SessionPersistenceJsonl, {
|
||||
@@ -122,6 +123,7 @@ export function composeTuiApp(ctx: Context, config: Config): void {
|
||||
const goals = config.goals ?? {}
|
||||
const persistenceRoot = config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT
|
||||
ctx.plugin(CommandService)
|
||||
ctx.plugin(commandFeedback)
|
||||
if (goals !== false) ctx.plugin(commandGoal)
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: persistenceRoot,
|
||||
|
||||
@@ -49,6 +49,7 @@ describe('dsh-tui-demo app', () => {
|
||||
|
||||
expect(calls.map(call => call.name)).toEqual([
|
||||
'CommandService',
|
||||
'command-feedback',
|
||||
'command-goal',
|
||||
'SessionPersistenceJsonl',
|
||||
'session-checkpoint-policy',
|
||||
@@ -61,14 +62,14 @@ describe('dsh-tui-demo app', () => {
|
||||
'tool-ask-user',
|
||||
])
|
||||
expect(calls[0]?.config).toBeUndefined()
|
||||
expect(calls[2]?.config).toEqual({ root: '/tmp/tui-sessions', compression: 'none' })
|
||||
expect(calls[4]?.config).toEqual({ path: join('/tmp/tui-sessions', 'session-query.db') })
|
||||
expect(calls[5]?.config).toEqual({
|
||||
expect(calls[3]?.config).toEqual({ root: '/tmp/tui-sessions', compression: 'none' })
|
||||
expect(calls[5]?.config).toEqual({ path: join('/tmp/tui-sessions', 'session-query.db') })
|
||||
expect(calls[6]?.config).toEqual({
|
||||
maxReferences: 2,
|
||||
candidateLimit: 7,
|
||||
maxReferenceBytes: 1234,
|
||||
})
|
||||
const tuiConfig = calls[8]?.config as { sessionId: string }
|
||||
const tuiConfig = calls[9]?.config as { sessionId: string }
|
||||
expect(tuiConfig).toMatchObject({
|
||||
welcome: 'TUI ready',
|
||||
resumeCommand: 'dsh --resume {session}',
|
||||
@@ -76,7 +77,7 @@ describe('dsh-tui-demo app', () => {
|
||||
maxToolOutputLines: 3,
|
||||
})
|
||||
expect(tuiConfig.sessionId).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
const spineConfig = calls[9]?.config as {
|
||||
const spineConfig = calls[10]?.config as {
|
||||
readonly agents: Array<Record<string, unknown>>
|
||||
readonly goals: Record<string, never>
|
||||
readonly maxParallelToolCalls: number
|
||||
@@ -109,11 +110,11 @@ describe('dsh-tui-demo app', () => {
|
||||
workspaceContext: false,
|
||||
})
|
||||
|
||||
expect(calls[2]?.config).toEqual({ root: './.sessions' })
|
||||
expect(calls[5]?.config).toEqual({})
|
||||
expect(calls[3]?.config).toEqual({ root: './.sessions' })
|
||||
expect(calls[6]?.config).toEqual({})
|
||||
// No configured welcome forwards none: the TUI banner sweeps in without a subtitle.
|
||||
expect(calls[8]?.config).toEqual({ sessionId: 'persisted-session' })
|
||||
expect((calls[9]?.config as { agents: Array<Record<string, unknown>> }).agents[0]).toMatchObject({
|
||||
expect(calls[9]?.config).toEqual({ sessionId: 'persisted-session' })
|
||||
expect((calls[10]?.config as { agents: Array<Record<string, unknown>> }).agents[0]).toMatchObject({
|
||||
id: 'main',
|
||||
resumeSessionId: 'persisted-session',
|
||||
})
|
||||
@@ -129,12 +130,14 @@ describe('dsh-tui-demo app', () => {
|
||||
workspaceContext: false,
|
||||
})
|
||||
|
||||
const tuiConfig = calls[7]?.config as { sessionId: string }
|
||||
const tuiConfig = calls[8]?.config as { sessionId: string }
|
||||
expect(tuiConfig.sessionId).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
expect((calls[8]?.config as { agents: Array<Record<string, unknown>> }).agents[0])
|
||||
expect((calls[9]?.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[8]?.config).toMatchObject({ goals: false })
|
||||
// `/feedback` is unconditional: disabling goals must not remove it.
|
||||
expect(calls.map(call => call.name)).toContain('command-feedback')
|
||||
expect(calls[9]?.config).toMatchObject({ goals: false })
|
||||
})
|
||||
|
||||
it('has the namespace-plugin export shape so the Loader keeps its schema', () => {
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
{
|
||||
"path": "../../goal/command-goal"
|
||||
},
|
||||
{
|
||||
"path": "../../feedback/command-feedback"
|
||||
},
|
||||
{
|
||||
"path": "../agent-spine-demo"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user