feat: 新增用户补充指引功能,含队列管理与 UI 交互 - #117
xinggitxing wants to merge 2 commits into
Conversation
|
Issues #113 【TUI】不打断任务的情况下,补充发送引导信息。 |
825125b to
753d364
Compare
…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
753d364 to
c3e7845
Compare
|
Implemented the "supplementary guidance" mechanism described in this issue (and #113): Two layers, so a new instruction never has to wait for the previous one:
If a turn would otherwise end while guidance is still queued, the loop stays alive for one more Notes compared to the design sketched here:
New tests: 9 core tests (queue semantics, the actual request payloads of a turn that receives 中文说明:AI 正在回复时, |
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
`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
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
概述
新增"补充指引"机制,允许用户在 LLM 正在处理提示词时(不中断当前轮次)发送额外指令。这实现了"边执行边引导"的交互模式,用户可输入指引文本进入队列,在下次 LLM API 调用前自动注入。
动机
此前当 LLM 忙碌时(例如在多步骤任务中执行工具调用),用户只能等待完成或直接中断智能体来发送新指令。本功能引入了一个非侵入式的补充消息队列:用户可以在智能体工作时输入并提交指引内容,系统会
将这些消息作为
system消息在下次 LLM API 调用前注入(跳过总结阶段)。核心变更
会话层(
src/session.ts)PendingSupplementary类型和MAX_SUPPLEMENTARY_QUEUE(每会话上限 10 条)MessageMeta.isSupplementary标记用于消息渲染addSupplementaryMessage、cancelSupplementaryMessage、countPendingSupplementary、listPendingSupplementary、isInSummaryPhaseflushSupplementaryMessages():将队列条目转换为system角色的SessionMessage[],内容前缀为[User Supplementary Guidance]\nactivateSession()中每次 LLM 调用前注入,但仅在 非总结阶段(上次 LLM 响应包含 tool_calls 时)isSummarizing标记跟踪 LLM 返回是否包含 tool_callsUI — App(
src/ui/App.tsx)supplementaryCount、supplementaryList、isSummarizingonSupplementaryStatusChanged回调将队列同步到 React 状态height: 0隐藏),防止视图切换时缓冲区丢失UI — PromptInput(
src/ui/PromptInput.tsx)isSummarizing时阻止输入,显示"等待总结完成..."UI — MessageView(
src/ui/components/MessageView/index.tsx)message.meta?.isSupplementary新增渲染分支:黄色边框的[Supplementary Guidance]信息块测试(
src/tests/session.test.ts)新增 10 个测试用例:
addSupplementaryMessage队列添加并返回 IDaddSupplementaryMessage队列满时返回 nullcancelSupplementaryMessage移除特定消息cancelSupplementaryMessage空会话返回 falseflushSupplementaryMessages返回正确角色和前缀的 system 消息isInSummaryPhase初始返回 falseonSupplementaryStatusChanged在添加和取消时被调用文件变更
src/session.tssrc/tests/session.test.tssrc/ui/App.tsxsrc/ui/PromptInput.tsxsrc/ui/components/MessageView/index.tsx测试结果
npm run check— 类型检查、代码规范、格式检查全部通过