Skip to content

feat: 新增用户补充指引功能,含队列管理与 UI 交互 - #117

Open
xinggitxing wants to merge 2 commits into
lessweb:mainfrom
xinggitxing:feat/supplementary-guidance
Open

xinggitxing wants to merge 2 commits into
lessweb:mainfrom
xinggitxing:feat/supplementary-guidance

Conversation

@xinggitxing

Copy link
Copy Markdown
Contributor

概述

新增"补充指引"机制,允许用户在 LLM 正在处理提示词时(不中断当前轮次)发送额外指令。这实现了"边执行边引导"的交互模式,用户可输入指引文本进入队列,在下次 LLM API 调用前自动注入。

动机

此前当 LLM 忙碌时(例如在多步骤任务中执行工具调用),用户只能等待完成或直接中断智能体来发送新指令。本功能引入了一个非侵入式的补充消息队列:用户可以在智能体工作时输入并提交指引内容,系统会
将这些消息作为 system 消息在下次 LLM API 调用前注入(跳过总结阶段)。

核心变更

会话层(src/session.ts

  • 新增 PendingSupplementary 类型和 MAX_SUPPLEMENTARY_QUEUE(每会话上限 10 条)
  • MessageMeta.isSupplementary 标记用于消息渲染
  • 新增方法:addSupplementaryMessagecancelSupplementaryMessagecountPendingSupplementarylistPendingSupplementaryisInSummaryPhase
  • 私有方法 flushSupplementaryMessages():将队列条目转换为 system 角色的 SessionMessage[],内容前缀为 [User Supplementary Guidance]\n
  • 补充消息在 activateSession() 中每次 LLM 调用前注入,但仅在 非总结阶段(上次 LLM 响应包含 tool_calls 时)
  • isSummarizing 标记跟踪 LLM 返回是否包含 tool_calls

UI — App(src/ui/App.tsx

  • 新增状态:supplementaryCountsupplementaryListisSummarizing
  • onSupplementaryStatusChanged 回调将队列同步到 React 状态
  • 忙碌结束逻辑自动将未刷新的补充文本回填到 PromptInput
  • PromptInput 始终保持挂载(通过 height: 0 隐藏),防止视图切换时缓冲区丢失

UI — PromptInput(src/ui/PromptInput.tsx

  • 补充消息列表 UI,支持上下导航、退格取消、回车提交
  • isSummarizing 时阻止输入,显示"等待总结完成..."
  • 忙碌但 总结时,回车提交内容作为补充指引
  • Prompt 草稿合并逻辑:将现有缓冲区文本与补充草稿合并

UI — MessageView(src/ui/components/MessageView/index.tsx

  • message.meta?.isSupplementary 新增渲染分支:黄色边框的 [Supplementary Guidance] 信息块

测试(src/tests/session.test.ts

新增 10 个测试用例:

  • addSupplementaryMessage 队列添加并返回 ID
  • addSupplementaryMessage 队列满时返回 null
  • cancelSupplementaryMessage 移除特定消息
  • cancelSupplementaryMessage 空会话返回 false
  • flushSupplementaryMessages 返回正确角色和前缀的 system 消息
  • 补充队列按会话隔离
  • isInSummaryPhase 初始返回 false
  • 补充列表返回副本(不可变)
  • onSupplementaryStatusChanged 在添加和取消时被调用

文件变更

文件 变更量
src/session.ts +100 行
src/tests/session.test.ts +166 行(10 个测试)
src/ui/App.tsx +121 / -33 行
src/ui/PromptInput.tsx +112 / -33 行
src/ui/components/MessageView/index.tsx +15 行

测试结果

  • npm run check — 类型检查、代码规范、格式检查全部通过
  • ✅ 10 个新增测试用例全部通过

@xinggitxing

xinggitxing commented May 23, 2026

Copy link
Copy Markdown
Contributor Author

Issues #113 【TUI】不打断任务的情况下,补充发送引导信息。

@xinggitxing
xinggitxing force-pushed the feat/supplementary-guidance branch from 825125b to 753d364 Compare May 25, 2026 23:03
…nd UI

- Add PendingSupplementary queue (max 10 per session) with add/cancel/flush lifecycle
- Inject supplementary messages as system messages before LLM calls (non-summarizing only)
- Track isSummarizing phase: LLM responses without tool_calls mark summary phase
- UI: Supplementary message list with up/down navigation, backspace cancel, enter submit
- UI: Auto-refill unflushed supplementary text to PromptInput when agent becomes idle
- UI: Keep PromptInput mounted (hidden via zero-height) to prevent buffer loss
- UI: Render supplementary guidance messages in MessageView with distinct styling
- Tests: 10 new test cases covering queue management, isolation, immutability, callbacks
@xinggitxing
xinggitxing force-pushed the feat/supplementary-guidance branch from 753d364 to c3e7845 Compare May 26, 2026 15:53
@mulyawansentosa

mulyawansentosa commented Sep 24, 2026

Copy link
Copy Markdown

Implemented the "supplementary guidance" mechanism described in this issue (and #113):
<PR_LINK>

Two layers, so a new instruction never has to wait for the previous one:

  1. Mid-turn injection. The prompt is queued on the session and, right before every LLM call of
    that same turn, it is appended to the conversation as a user message (with images and
    selected skills). The model reads it together with the work it already did and can revise or
    supersede the earlier instructions:

    user:      install nginx
    assistant: <tool_calls>
    tool:      <result>
    user:      stop, use apache instead      ← guidance, read by the next LLM call
    assistant: <revised plan>
    
  2. Steering a streaming answer. Injection can only happen at a request boundary, so a long
    text answer would still block it. When a prompt arrives while an answer is streaming, that one
    request is aborted, the text produced so far is kept as an assistant message, and the loop
    immediately issues a new request carrying the guidance:

    user:      explain how the deploy works
    assistant: The deploy runs in three stag…       ← cut short
    user:      no, just tell me the rollback step   ← read straight away
    

    Steering is opt-in: Ctrl+Enter steers a single message, and steerMode: "interrupt" in
    settings.json makes plain Enter steer too ("queue" is the default). Tools are never
    interrupted: steering only targets the model stream, so a running command finishes and the
    guidance is injected at the next request boundary.

If a turn would otherwise end while guidance is still queued, the loop stays alive for one more
iteration, so pending guidance is never silently dropped or attached to a later, unrelated prompt.
Guidance is not injected while the session waits for the user (an ask_user_question answer or a
permission decision), so it can never be consumed as that answer. Esc still interrupts the turn.

Notes compared to the design sketched here:

  1. The injected message uses role: "user" with meta.isSupplementary, not a system reminder.
    A user message is the strongest signal for superseding earlier instructions, it keeps the
    prompt verbatim in the transcript, and it carries image attachments natively. system remains
    an option if you prefer the softer "guidance" framing.
  2. The implementation is split into two commits (injection, then steering) so the turn-boundary
    behaviour can be reviewed on its own.

New tests: 9 core tests (queue semantics, the actual request payloads of a turn that receives
guidance mid-flight, the aborted stream with its follow-up request, and the no-op case) plus 4 Ink
integration tests for the input wiring. npm run check and npm test (333 CLI + 392 core + 59
VS Code tests) pass.

中文说明:AI 正在回复时,Enter 不再被拒绝,也不必等到本轮结束。实现分两层:(1) 消息进入会话队列,
在本轮每次 LLM 调用前作为 user 消息(含图片与 skills)注入,模型能结合已完成的工作修改甚至推翻
之前的指令;(2) 如果模型正在流式输出回答,则该次请求会被中断,已生成内容保留为 assistant 消息
(标记 meta.interrupted),并立即发起携带新指令的新请求。正在执行的命令不会被中断,steering 只作用
于模型输出流。steering 默认是 opt-in:Ctrl+Enter 立即转向;steerMode 默认 "queue",设为
"interrupt" 后普通 Enter 也会截断当前输出。

mulyawansentosa added a commit to Code-AI-Tech-ID/deepcode-cli that referenced this pull request Sep 24, 2026
Guidance injected before the next LLM call still had to wait for the answer that was
already streaming, so a long text answer could not be redirected until it finished on
its own. No API can rewrite output that has already been generated, so the only way to
have the guidance read sooner is to abort that one request and ask again.

The session now tracks the streaming request separately from the turn. When the user
asks to steer, that request is aborted, whatever the model already wrote is kept as an
assistant message marked meta.interrupted, and the loop immediately issues a new
request that carries the guidance. Tools are never interrupted: steering only targets
the model stream, so a running command finishes first and the guidance is injected at
the next request boundary.

Steering is opt-in, so an existing setup keeps behaving exactly as before:

- Ctrl+Enter steers that one message.
- steerMode: "interrupt" makes plain Enter steer as well; the default is "queue".

- session: steerActiveSession(), a per-request steer signal released once the request
  settles, and LlmSteeredError carrying the partial answer
- session: a steered answer is not an interruption - the turn continues, the status
  stays completed, and the aborted request is not retried
- settings: steerMode ("queue" by default)
- cli: Ctrl+Enter is parsed from its CSI-u sequence, forwarded as
  PromptSubmission.steer, and the cut-short answer is labelled in the transcript;
  the footer mentions the shortcut
- tests: core covers the aborted stream, the follow-up request payload, the kept
  partial answer and the no-op case; the cli covers the key parsing, the steering
  submit, and steerMode resolution
- docs: configuration table and section, README and quickstart key tables

Refs lessweb#113, lessweb#117
mulyawansentosa added a commit to Code-AI-Tech-ID/deepcode-cli that referenced this pull request Sep 24, 2026
`handleSubmit` treated every submission as guidance while a turn was running, including
the `/exit` command that `PromptInput` deliberately lets through. The CLI then queued
"/exit" as a user message and steered the turn instead of quitting.

Commands are not prompts, so they take the normal path again: non-exit commands stay
blocked while the turn runs, and `/exit` still quits.

Found by driving the real TUI in a pseudo-terminal against a stub OpenAI-compatible
server. The guidance path itself behaved as designed: the streaming answer was aborted
after 21 of 130 chunks, the partial text was kept and marked `meta.interrupted`, and the
follow-up request carried it together with the guidance as a user message.

Refs lessweb#113, lessweb#117
mulyawansentosa added a commit to Code-AI-Tech-ID/deepcode-cli that referenced this pull request Sep 24, 2026
10 was a conservative guess rather than a measured limit. The queue drains at the next
request boundary, so this only caps how many instructions can pile up faster than one
step of the turn (a long tool run or a long streaming answer).

Raising it to 20 keeps that burst covered while still bounding the cost: every queued
prompt travels with each remaining request of the turn, so an unbounded queue could
inflate the context (and any attached images) without limit.

- the queue is not dropped or reordered; it still drains in FIFO order before every
  LLM call of the turn
- when the limit is reached the prompt is refused but stays in the input box, so
  nothing the user typed is lost

Refs lessweb#113, lessweb#117
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants