Skip to content

feat(runtime): implement ask_user tool for plan-mode user interaction#582

Merged
pionxe merged 7 commits into1024XEngineer:mainfrom
Cai-Tang-www:feat/issue-578-ask-user-a
May 9, 2026
Merged

feat(runtime): implement ask_user tool for plan-mode user interaction#582
pionxe merged 7 commits into1024XEngineer:mainfrom
Cai-Tang-www:feat/issue-578-ask-user-a

Conversation

@Cai-Tang-www
Copy link
Copy Markdown
Collaborator

Summary

  • Add ask_user tool with JSON schema, blocking broker, and plan-mode-only visibility
  • Introduce ActionTypeInteraction security type for user interaction tools
  • Implement gateway protocol with user_question_answer frame validation
  • Add comprehensive tests: tool visibility/mode guard, broker lifecycle, gateway validation, permission regression

Related Issue

Closes #578

Test plan

  • Tool tests: defaults, schema, nil broker, invalid args, happy path, timeout, emitter safety, late binding
  • Mode filtering: visible in plan/read-only, hidden in build, blocked execution in non-plan
  • Broker tests: nil receiver, open-resolve-close, unknown request, duplicate idempotent, timeout, cancel, concurrent
  • Runtime tests: validation, invalid status, success/skip flows, context cancel, adapter conversion
  • Gateway validation: valid answered/skipped/empty, missing payload, missing request_id, invalid status
  • Permission regression: all existing TestBuildPermissionAction and TestValidateFrame tests pass

🤖 Generated with Claude Code

Add backend closed-loop for ask_user: tool schema with blocking broker,
plan-mode-only visibility, ActionTypeInteraction security type, and
gateway protocol with user_question_answer frame validation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

fennoai[bot]

This comment was marked as outdated.

- Replace sequential ask-N IDs with crypto/rand hex IDs in Broker
- Add Broker.PendingIDs() for test-time ID discovery
- Use emitRunScopedPriority for critical user_question_requested
  event to avoid silent drops under channel pressure
- Update all tests to resolve via dynamic ID instead of hardcoded
  "ask-1"

Addresses PR 1024XEngineer#582 review from fennoai.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Cai-Tang-www
Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review. Here's what was addressed in da38a6f:

1. Predictable request IDs (high) — Fixed. Broker.Open now generates IDs via crypto/rand (ask-<16randhex>). Added PendingIDs() for test discovery. All tests updated to resolve via dynamic ID instead of hardcoded "ask-1".

2. Event delivery reliability (medium) — Fixed. The critical user_question_requested event now uses emitRunScopedPriority (drops old event to make room) instead of emitRunScopedOptional (silently drops new event). Resolution events (user_question_answered/skipped/timeout) remain optional since the broker will time out and deliver those statuses via the tool result channel regardless.

3. TUI event wiring — This is intentional scope boundary. Issue #578 Plan A is explicitly Tool + Runtime + Gateway only (no TUI). The event types are defined and emitted so the TUI layer can consume them in a follow-up. The runtime contract extension and TUI rendering are separate work.

4. Remote adapter wiring — Same as #3. This is the backend closed-loop. The gateway protocol (FrameActionUserQuestionAnswer) is fully wired through validation, handler, and RPC dispatch. External adapter support is a follow-up task.

All tests pass with no regressions.

@Cai-Tang-www
Copy link
Copy Markdown
Collaborator Author

/review

Copy link
Copy Markdown

@fennoai fennoai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a few blocking issues in the new ask_user flow. The main one is that the request event is emitted before the broker-generated request_id exists, so clients cannot send a valid gateway.user_question_answer back. I also found one security issue in request-id generation and one error-classification regression in the tool error path.

Comment thread internal/tools/ask_user_tool.go Outdated

// emit user_question_requested before blocking
if call.AskUserEventEmitter != nil {
call.AskUserEventEmitter("user_question_requested", request)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_question_requested is emitted before broker.Open returns, so this payload never contains the broker-generated request_id. That breaks the new control-plane round trip: gateway.user_question_answer requires payload.request_id, but the client never receives one here. As written, the first ask_user call will block until timeout because the UI/gateway has no valid identifier to answer.

Comment thread internal/runtime/askuser/broker.go Outdated
// newRequestID generates an unguessable request ID using crypto/rand.
func newRequestID() string {
buf := make([]byte, 8)
_, _ = rand.Read(buf)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignoring the rand.Read error defeats the “unguessable ID” guarantee. On entropy failure buf stays zeroed, so every request becomes the same ask-0000000000000000 ID, which can overwrite an in-flight entry in pending and misroute or hijack answers. This should fail closed or retry instead of silently accepting a predictable/colliding ID.

QuestionID: request.QuestionID,
Message: err.Error(),
})
return ToolResult{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error branch returns err != nil with ToolResult.IsError still false. The runtime persists tool messages based on result.IsError, so an ask_user timeout/cancellation will be recorded as a successful tool result instead of a failed one. That regresses the existing tool-error invariant and can mislead both the model and the UI about what happened.

@Cai-Tang-www
Copy link
Copy Markdown
Collaborator Author

/code CI炸了,找下原因修复一下,修好推送到我的分支

@fennoai
Copy link
Copy Markdown

fennoai Bot commented May 8, 2026

Result

已定位并修复两个 CI 问题,变更已推到 Cai-Tang-www:feat/issue-578-ask-user-a,提交是 b1807646

  1. 补齐了 ask_user 相关测试覆盖,覆盖到之前 Codecov 点名的新增分支,包括 ResolveUserQuestion 默认状态、adapter 错误路径、gateway user_question_answer payload 解码分支、MultiWorkspaceRuntime.ResolveUserQuestion 透传,以及只读模式下 ActionTypeInteraction / todo_write 权限判断。
  2. 修复了真实的全量测试编译失败:internal/app/bootstrap_test.go 里的 stubRemoteRuntimeForBootstrap 缺少新接口 ResolveUserQuestion,导致 internal/app 包无法通过 go test ./...

Verification

本地已通过 go test ./...

View job run

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: Cai-Tang-www <106404101+Cai-Tang-www@users.noreply.github.com>
@Cai-Tang-www
Copy link
Copy Markdown
Collaborator Author

/code 提高测试覆盖率,然后推送到我的分支

@fennoai
Copy link
Copy Markdown

fennoai Bot commented May 9, 2026

Result

已提高这次 ask_user 相关改动的测试覆盖率,重点补在 TUI 新增分支上:

  • internal/tui/core/app/update_permission_test.go:补了 submitPendingUserQuestionInput 的主要分支,覆盖空请求、禁止跳过、必填空输入、single_choice/multi_choice 校验、允许跳过、文本回答,以及 userQuestionResolutionFinishedMsg 成功/失败和 RuntimeClosedMsg 清理分支。
  • internal/tui/core/app/user_question_prompt_test.go:补了 payload 指针解析、提示文本 fallback/渲染、以及 answered / skipped / timeout / 默认 resolved 状态分支。

Verification

已通过 go test ./...

Push

提交:b2451cf0 (test(tui): cover ask_user interaction branches)
已推送到现有 PR 分支 Cai-Tang-www:feat/issue-578-ask-user-a
另外按 fork 流程同步生成了 fork 内部 PR:Cai-Tang-www#60

View job run

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: Cai-Tang-www <106404101+Cai-Tang-www@users.noreply.github.com>
@Cai-Tang-www
Copy link
Copy Markdown
Collaborator Author

/code CI炸了,找下原因修复一下,修好推送到我的分支

@fennoai
Copy link
Copy Markdown

fennoai Bot commented May 9, 2026

Result

已修复这次 CI 失败,并推送到当前 PR 分支。

这次炸掉的不是 ask_user 主逻辑,而是 internal/runner 的既有测试在 go test ./... -covermode=atomic 下对网络错误文案断言过严。GitHub Actions 上实际返回的是 read message: ... connection reset by peer,但测试只接受更窄的握手报错形式,导致 build-test 在 coverage 步骤失败。

修复内容:

  • 调整 internal/runner/runner_test.go 的握手失败断言,改为校验是否属于握手阶段相关错误:authenticate:register runner:read message:
  • 保持测试目标不变,只去掉对具体 socket 时序和平台文案的脆弱依赖。

Verification

已通过:

  • go test ./internal/runner -covermode=atomic -count=20
  • go test ./... -covermode=atomic -coverprofile=/tmp/coverage.out
  • go build ./...
  • make docs-gateway-check

Push

提交:6605b0a9 (test(runner): tolerate handshake transport errors)
已推送到当前 PR 分支。
Fork 内辅助 PR 已更新/创建:Cai-Tang-www#61

View job run

xgopilot and others added 2 commits May 9, 2026 01:50
Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: Cai-Tang-www <106404101+Cai-Tang-www@users.noreply.github.com>
test(runner): tolerate handshake transport errors
@pionxe pionxe merged commit 5818974 into 1024XEngineer:main May 9, 2026
3 checks passed
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.

[实现] AskUser 子任务A:工具与 Runtime/Gateway 协议闭环

3 participants