Skip to content

修复(OMP):终端原生 Ask 与 CodeIsland 并行回答#264

Draft
haixing23 wants to merge 2 commits into
wxtsky:mainfrom
haixing23:fix/omp-native-ask-race
Draft

修复(OMP):终端原生 Ask 与 CodeIsland 并行回答#264
haixing23 wants to merge 2 commits into
wxtsky:mainfrom
haixing23:fix/omp-native-ask-race

Conversation

@haixing23

Copy link
Copy Markdown
Contributor

关联

本 PR 主要用于确认实现方向,暂不请求立即合并。如果维护者更倾向于 OMP 提供正式扩展接口,我可以据此缩小或调整实现。

问题背景

#244 为 OMP 增加了 ask 回答回填,但原来的实现是在 tool_call extension handler 中阻塞等待 CodeIsland 返回答案。在这段等待期间,OMP 原生终端选择框按设计还没有打开。

#256 通过强制展开 CodeIsland 问题卡片解决了互相等待的问题,但这仍然只能依赖 CodeIsland 回答,无法实现「终端和 CodeIsland 同时可答、先回答的一端生效」。

修改内容

1. 并行启动 OMP 原生 Ask 与 CodeIsland 提问

OMP 扩展注册同名 ask 工具,执行时同时启动两个回答入口:

  • 复用 OMP 内置 AskTool(终端渲染、多问题导航、超时、推荐项等);
  • 向 CodeIsland 发送阻塞式 AskUserQuestion

任意一端先返回后立即取消另一端(settle 幂等,并发完成安全)。终端和 CodeIsland 可以同时回答,不再依赖 handler 超时后才显示终端界面。

2. 复用原生 AskTool

shadow ask 直接调用 OMP 提供的 AskTool,并复用其 labeldescription,保留 OMP 对 agent 的完整行为约束(默认行动、先查现有信息、仅在重大 tradeoff 时提问等)。

3. 精确恢复「智能抑制」

扩展在确认原生 Ask 正在并行运行时附带 _codeisland_native_ask_racing: true。CodeIsland 只对带此标记的 OMP 提问应用 Smart Suppress;Pi 和旧版 OMP 仍强制展开。

队列提升时也重新计算展示状态:若前一问题将 .questionCard 展开,新的 OMP 提问会被折叠(不影响 .sessionList)。

4. 取消不会挂死

任何一端(回答、跳过、取消、失败)完成后立即取消另一端并 settle。TUI Esc 取消会立即 cancel island bridge,不再等 24h。

5. 结构化回答

CodeIsland 回答分别保留 selectedOptionscustomInput、多选合并结果,避免通过逗号反解析产生歧义。该 sidecar 受 source == pi && toolUseId 限制,非 OMP 来源仍消费原有 answer 字符串。

6. 共享问题卡片修复

multiSelect=true 且无预设选项时,问题卡片现在显示可用的纯文本提交按钮(所有走共享卡片的来源共享此修复)。

7. 补充 _pi_tool_call_id 解析

HookEvent.toolUseId 增加对 _pi_tool_call_id 的识别。

8. OMP extension 版本 v2 → v4

已知限制

  • ToolDefinition 没有 loadMode 字段。原生 AskToolloadMode = "discoverable" 无法通过扩展 API 复用,因此在 tools.discoveryMode = "all" 下 shadow ask 会始终激活。如 OMP 后续支持该字段,可以补上。
  • shadow ask 无法携带原生 examples/loadMode 等 OMP 内部元数据。

降级行为

  • CodeIsland 未运行或 bridge 不可用:OMP 原生 Ask 正常工作
  • 用户先在终端回答:取消 CodeIsland 等待
  • 用户先在 CodeIsland 回答:取消原生 Ask,答案返回给 agent
  • 一端跳过或取消:另一端也被取消
  • Pi 和旧版 OMP:继续强制展开

测试覆盖

  • AppStateQuestionFlowTests:21/21(含新增混合来源队列提升回归)
  • ConfigInstallerTests:57/57
  • HookEventToolUseIdTests:9/9
  • OMP 16.3.10 strict TypeScript typecheck:通过
  • 原生 AskTool 复用探针:通过
  • 真实 OMP Ask 冒烟:通过

验证

swift test --filter 'AppStateQuestionFlowTests|ConfigInstallerTests|HookEventToolUseIdTests' -Xswiftc -Xfrontend -Xswiftc -enable-constraint-solver-performance-hacks

Register a shadow 'ask' tool that concurrently starts OMP's built-in
AskTool (terminal rendering, navigation, timeout, multi-question wizard)
and a blocking CodeIsland AskUserQuestion. Whichever side returns a real
answer first wins; the loser is cancelled immediately.

This replaces the previous flow where the extension handler blocked
inside tool_call until CodeIsland answered, delaying the terminal dialog
(wxtsky#244 -> wxtsky#256). Users can now answer from either the terminal or the
CodeIsland/iPhone/Watch UI without waiting for a handler timeout.

Key changes:
- Shadow ask reuses pi.pi.AskTool for its executor and LLM-facing
  label/description, preserving OMP's behavioral guidance.
- Bridge request carries _codeisland_native_ask_racing so AppState can
  scope Smart Suppress: only OMP requests with this marker may stay
  collapsed when the terminal is frontmost. Pi and legacy OMP keep the
  forced-expand fallback from wxtsky#256.
- Genuine native AskTool failure cancels the island bridge and
  propagates immediately instead of waiting for a 24h island timeout.
- Structured answer details (selectedOptions, customInput) are passed
  back via _codeislandAnswerDetails, scoped to pi source + toolUseId.
- QuestionBar preserves selectedOptions/customInput for all sources;
  non-OMP paths still consume the original answer string unchanged.
- multiSelect=true with empty/no options now shows a usable Submit
  button for free-text input (shared fix).
- HookEvent parses _pi_tool_call_id for OMP tool call correlation.
- OMP extension version bumped v2 -> v4.
- Native AskTool reuse probe, 20 AppStateQuestionFlowTests, and 9
  HookEventToolUseIdTests all pass.
P1: When Smart Suppress kept the CodeIsland ask card collapsed, a native
TUI cancel (Esc) returned null but the gate still waited for the hidden
island bridge (24h timeout). Now any side finishing — answer, skip,
cancel, or failure — immediately cancels the other and settles. No more
per-side done/failed tracking; settle() is idempotent.

Smart Suppress: showNextPending() left an inherited .questionCard surface
expanded when the promoted OMP ask should be collapsed. Now folds it to
.collapsed (but never touches .sessionList).

Also: reuse native AskTool label/description instead of a one-liner;
remove dead tuiFailed/tuiError code paths; add regression test for
non-OMP -> OMP mixed-source queue promotion.
@haixing23
haixing23 marked this pull request as draft July 15, 2026 10:43
nguyenvanduocit pushed a commit to nguyenvanduocit/CodeIsland that referenced this pull request Jul 16, 2026
Upstream quiet since v1.0.30 (Jul 10). One new watch item:
- T-076: panel intermittently shifts from notch to upper-left corner
  on macOS 27.0 beta (issue wxtsky#263, M5 Pro, no upstream fix yet)

Skip: PR wxtsky#264 (OMP), PR wxtsky#262 (Cursor Tasks), issue wxtsky#265 (Cursor) —
all non-Claude CLIs or Cursor-specific.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5RZPP2LLwKBjbXuqKxDjf
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.

1 participant