mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
* fix(cursor): intercept fabricated 'Questions skipped' AskQuestion result in headless mode (#784) When cursor-agent runs under `--print --output-format stream-json` (HAPI's current Cursor remote launcher), the CLI returns a synthetic `Questions skipped by the user, continue with the information you already have` response for the `AskQuestion` tool in ~zero seconds with no error flag, because there is no IDE surface to render the question. The underlying model can interpret this as legitimate user consent and act on it. This patch intercepts the synthetic result in `cli/src/cursor/utils/cursorEventConverter.ts` and rewrites the `tool_call`/completed event to a structured `no_input_surface` failure (`status: 'failed'`, which downstream becomes `is_error: true`). Detection has two strategies: 1. String match - any `tool_call`/completed payload whose serialized form contains the synthetic-skip marker is rewritten. This is robust to wherever cursor-agent stuffs the marker inside the `tool_call` object. 2. Timing + name heuristic (defense in depth) - any completion that arrives within 500 ms of its 'started' event with a trivial result, for a tool call named `AskQuestion`, `askQuestion`, `ask_question`, or the converter's `unknown` fallback, is also rewritten. This catches the case where cursor-agent changes the synthetic-string text in a future release. The converter tracks per-call timestamps in a bounded `Map` (`<= 1024` entries, oldest evicted on overflow) and clears entries when the corresponding 'completed' event arrives. A small test-only reset hook isolates state between Vitest cases. This is a transitional safety patch. It auto-deletes when #781's ACP launcher replaces the stream-json launcher and `cursor/ask_question` becomes a proper bidirectional ACP method where fabrication is structurally impossible. Scope is intentionally tiny: only `cli/src/cursor/utils/cursorEventConverter.ts`, its colocated Vitest file, and a section in `docs/guide/cursor.md`. No changes to `cursorRemoteLauncher.ts`, ACP code, web normalizer, or permission UI. Refs: tiann/hapi#781 (long-term resolution via ACP migration) Closes: tiann/hapi#784 * fix(cursor): gate AskQuestion intercept on tool name (#784 PR #801 review) Address regression flagged by the HAPI auto-review bot on #801: `containsSyntheticSkipMarker` previously stringified the entire `tool_call` payload and matched the literal marker substring. Because this PR also adds that exact marker to `docs/guide/cursor.md` (to document the intercept), a Cursor `read_file` of that documentation page would surface the marker inside `readToolCall.result.content` and be rewritten as a `no_input_surface` failure, corrupting an unrelated, legitimate result. The intercept is now gated on the tool name resolving to an AskQuestion-shaped call (`AskQuestion`, `askQuestion`, `ask_question`, or the converter's `unknown` fallback for unnamed function-shaped tools). `read_file` / `write_file` tool calls - which have explicit `read_file` and `write_file` names from `extractToolName` - no longer fall under the intercept, regardless of what their payload contains. The marker check itself now walks values recursively (string / array / object), guarded by a `WeakSet` against cycles, instead of relying on `JSON.stringify`. Slightly tidier; behaviour is otherwise unchanged for the AskQuestion path. Regression tests added: - `read_file` result whose `content` contains the marker -> passes through with `status: 'completed'` and no `no_input_surface`. - `write_file` whose serialized `args` contain the marker -> same. - A non-AskQuestion function tool (`MyCustomTool`) whose result quotes the marker -> same. All 846 cli tests pass (17 in this file). `bun run typecheck` exits 0. * fix(cursor): scope synthetic-skip check to extracted result (#784 PR #801 review-2) Address second Major finding from the HAPI auto-review bot on #801: After the previous fix gated the intercept on the tool name, the marker check still recursed into the entire `tool_call` object - which includes `function.arguments`, the agent's own prompt text. A legitimate AskQuestion whose prompt quotes the synthetic-skip marker (e.g. an agent debugging this exact bug, or any prompt that pastes the marker verbatim) would have been rewritten as `no_input_surface` even when the operator actually answered. Changes: 1. `extractToolResult` now extracts the cursor-side response from function-shaped tool calls. Previously it returned `{}` for anything that wasn't `readToolCall` or `writeToolCall`. It now returns `function.result` when present, otherwise every field of `function` except `name` and `arguments`. This excludes the agent's input from what downstream sees as the tool result, and as a side effect surfaces the actual cursor response for function-shaped tools (which was previously lost - see the #784 incident note about HAPI storing `output: {}` for AskQuestion in the message DB). 2. `shouldRewriteAsNoInputSurface` now searches only the extracted `result`, not the whole `tool_call`. The bot's exact recommendation. 3. Test added: an AskQuestion whose `arguments` quote the marker but whose `result` is a real user answer, with elapsed time past the 500 ms threshold so the timing heuristic does not apply. Asserts the tool_result passes through with `status: 'completed'` and the operator's actual answer. All 847 cli tests pass (18 in `cursorEventConverter.test.ts`). `bun run typecheck` exits 0. The widened `extractToolResult` scope is necessary for the marker check to actually find the synthetic string (it lives inside `function.result` or a sibling field), and is the bot's explicit recommendation. It also removes the long-standing data-loss bug where AskQuestion responses were surfaced to the message DB as opaque `{}` - regardless of fabrication.
73 lines
3.4 KiB
Markdown
73 lines
3.4 KiB
Markdown
# Cursor Agent
|
|
|
|
HAPI supports [Cursor Agent CLI](https://cursor.com/docs/cli/using) for running Cursor's AI coding agent with remote control via web and phone.
|
|
|
|
## Prerequisites
|
|
|
|
Install Cursor Agent CLI:
|
|
|
|
- **macOS/Linux:** `curl https://cursor.com/install -fsS | bash`
|
|
- **Windows:** `irm 'https://cursor.com/install?win32=true' | iex`
|
|
|
|
Verify installation:
|
|
|
|
```bash
|
|
agent --version
|
|
```
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
hapi cursor # Start Cursor Agent session
|
|
hapi cursor resume <chatId> # Resume a specific chat
|
|
hapi cursor --continue # Resume the most recent chat
|
|
hapi cursor --mode plan # Start in Plan mode
|
|
hapi cursor --mode ask # Start in Ask mode
|
|
hapi cursor --yolo # Bypass approval prompts (--force)
|
|
hapi cursor --model <model> # Specify model
|
|
```
|
|
|
|
## Permission Modes
|
|
|
|
| Mode | Description |
|
|
|------|-------------|
|
|
| `default` | Standard agent behavior |
|
|
| `plan` | Plan mode - design approach before coding |
|
|
| `ask` | Ask mode - explore code without edits |
|
|
| `yolo` | Bypass approval prompts |
|
|
|
|
Set mode via `--mode` flag or change from the web UI during a session.
|
|
|
|
## Modes
|
|
|
|
- **Local mode** - Run `hapi cursor` from terminal. Full interactive experience.
|
|
- **Remote mode** - Spawn from web/phone when no terminal. Uses `agent -p` with `--output-format stream-json` and `--trust`. Each user message spawns one agent process; session continues via `--resume`.
|
|
|
|
## Limitations
|
|
|
|
- **Tool approval** - In remote mode, `--trust` is used; tools run without per-request approval. Use `--yolo` for full bypass.
|
|
- **Session resume** - Pass `--resume <chatId>` or `--continue` to resume. Use `agent ls` to list previous chats and get chat IDs.
|
|
|
|
### Headless safety: AskQuestion behavior
|
|
|
|
When running cursor-agent under `--print --output-format stream-json` (HAPI's current remote mode), the cursor-agent CLI returns a synthetic `Questions skipped by the user, continue with the information you already have` response for the `AskQuestion` tool because there is no IDE surface to render the question. The agent's underlying model can interpret this as legitimate user consent and act on it.
|
|
|
|
HAPI intercepts this synthetic response in the stream-json event converter and rewrites it to an explicit `no_input_surface` error (`is_error: true`), so agents do not act on fabricated user consent. Defense-in-depth: any `AskQuestion` (or `name=unknown`) tool completion that arrives within ~500 ms of its start event with a trivial payload is treated the same way, in case cursor-agent changes the synthetic-string text in a future release.
|
|
|
|
Agents running under HAPI's Cursor remote mode should fall back to plain-text prompting (markdown options + waiting for a regular user message) until the [ACP migration (tiann/hapi#781)](https://github.com/tiann/hapi/issues/781) lands and `cursor/ask_question` becomes available as a proper bidirectional ACP method. At that point this intercept becomes unnecessary and is removed.
|
|
|
|
Tracking issue: [tiann/hapi#784](https://github.com/tiann/hapi/issues/784).
|
|
|
|
## Integration
|
|
|
|
Once running, your Cursor session appears in the HAPI web app and Telegram Mini App. You can:
|
|
|
|
- Monitor session activity
|
|
- Approve permissions from your phone
|
|
- Send messages when in local mode (messages queue for when you switch)
|
|
|
|
## Related
|
|
|
|
- [Cursor CLI Documentation](https://cursor.com/docs/cli/using)
|
|
- [How it Works](./how-it-works.md) - Architecture and data flow
|