feat: add Ask AI in-plan Q&A via a dedicated read-only subagent (fixes #1) - #3
Open
YeKc1M wants to merge 14 commits into
Open
feat: add Ask AI in-plan Q&A via a dedicated read-only subagent (fixes #1)#3YeKc1M wants to merge 14 commits into
YeKc1M wants to merge 14 commits into
Conversation
- Add Ask AI channel and endpoint to handle plan-related queries. - Define interfaces for request and response structures. - Implement request validation and history management. - Create a handler to manage subagent interactions and responses. - Add tests for client and host functionalities, including error handling and request processing. - Enhance UI tests to cover Ask AI interactions and responses.
bad-request responses carried details:{} while DSH's closed RPC taxonomy
requires details.issues for that code; the composed web client validates
the envelope with serverResponseSchema.parse and rejected the whole
response, surfacing a raw ZodError in the panel instead of the friendly
message. Emit per-field zod-shaped issues and collect every violation at
once; the client now also maps any ZodError from the connection caller to
a friendly internal error instead of leaking the dump.
An over-long answer was stored verbatim and re-sent as the next follow-up's history entry, where the host rejects answers beyond 32k chars — silently poisoning every later question. Cap the returned answer at the history budget with a visible marker (marker counted inside the cap), and slice history entries defensively on the client as well.
An over-long pasted question failed once and then kept failing on every Retry, because sendAsk and retryAsk sent the entry values verbatim while the host rejects anything beyond 8k chars. Slice question and quote to the host budgets on send and on retry, and hoist the budget constants shared with the history slice. The selection anchor already caps quotes at 800 chars, so the quote slice is budget alignment; the question slice fixes the reachable dead-end.
Two related cancellation defects: a host-side cancelled error was mapped to AbortError and silently deleted the question, and the thread was component state with an unmount abort, so navigating into the answering subagent cancelled it and lost the thread. Move the thread into a module-level store that persists to local storage, stop aborting on unmount so in-flight answers land after the panel remounts, and keep host cancellations visible as error entries while only the user's own Stop drops the question.
…n accept busy is already derived from the thread store's entries (state), not a ref; this documents the gate invariant so a future timeout/cancel path cannot desync the send gate from the button states, and makes send return whether the question was accepted so the panel clears the composer only on accept.
Ask AI no longer opens as an on-demand overlay sidebar; the review panel workspace is now a horizontal split with the Ask AI thread and composer permanently on the left of the plan preview and annotations. Selecting plan text and choosing the Ask AI action stages the excerpt straight into the always-visible composer. The docked panel widens to keep both halves readable.
The Ask AI budgets, channel/endpoint names, and request types were defined twice (src/client vs src/host) and kept in sync by comments alone. Move them into a single src/shared/limits.ts that both the browser bundle and the Host import and re-export, so the client slicing and the Host validation can never drift. The magic history slice (-20) now names MAX_HISTORY_ENTRIES; build.mjs needs no change since esbuild and the TS program follow imports.
Three client modules carried identical randomUUID-with-fallback ID generators (ask-thread, ask-ai, PlannotatorPanel); replace them with one shared newId(prefix) in src/shared/id.ts, preserving the legacy fallback formats (ask-/annotation-). PlannotatorPanel repeated a 3-line selection reset at six sites; extract clearSelection() (removeAllRanges + selection + comment) and adoptSelection() (set selection + clear comment + disarm approve-confirm). Deliberately untouched: Escape/Cancel keep their 2-line clear without removeAllRanges, and the delete-annotation/overall-feedback paths keep confirmApprove-only resets, so behavior is byte-identical.
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Closes #1
摘要
实现 #1 请求的 Ask AI 功能:审阅 plan 时选中一段内容(或直接提问),
由 agent 即时给出上下文相关的解释性回答,不必解散 gate 或写成修改意见。
变更
Host 半区
/dsh-plannotatorRPC 通道(单ask端点,trusted-host权威,复用loopback 放行/跨站 403 的信任栅栏)。
ctx.agents.get(sessionId)取被审阅会话的活 agent,用一次性只读子 Agent(label
plan-ask,优先 fork 继承模型/preset/已完成轮次,无 fork 时退化为全新 spawn)回答。toolFilter.allow逐项探测(read/grep/glob/web_search/web_fetch)+persona 约束双保险,子 Agent 无法 edit/write/bash、无法调用 exit_plan_mode。
bad-request 返回 schema 合规的
details.issues。Client 半区
测试
载验证(v0.1.4,通道启动即挂载)。
备注