feat(bin): add Cursor harness adapter support - #1726
Draft
vipentti wants to merge 72 commits into
Draft
Conversation
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
from
August 5, 2026 06:50
3dbb21e to
02b08dd
Compare
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
from
August 5, 2026 07:37
65dd134 to
3b84259
Compare
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
from
August 5, 2026 10:31
406afee to
59d19df
Compare
vipentti
marked this pull request as draft
August 5, 2026 10:36
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
2 times, most recently
from
August 5, 2026 11:27
54afe2e to
660a09e
Compare
vipentti
marked this pull request as ready for review
August 5, 2026 12:13
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
2 times, most recently
from
August 5, 2026 14:53
3c216d2 to
5b21865
Compare
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
from
August 5, 2026 18:59
4341c86 to
a45299f
Compare
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
4 times, most recently
from
August 6, 2026 03:01
2398b85 to
77c909c
Compare
Owner
|
Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch. When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again. Noted for firstmate#1726 at |
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
from
August 6, 2026 18:40
58ae208 to
fb4ac91
Compare
Owner
|
Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch. When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again. Noted for firstmate#1726 at |
…ases Cursor identity was wide enough to misfire in several owners: any executable named `agent`, and any path with an `agent/` directory component, classified as Cursor across harness detection, session-lock ancestry, tmux liveness, and worker-server discovery. The same generic name satisfied local spawn resolution and remote readiness, so firstmate could launch an unrelated executable with Cursor's flags or report a host ready with no Cursor installed. Introduce bin/fm-cursor-lib.sh as the single owner of Cursor executable resolution, the legacy-alias proof, Cursor process identity, and the shared process-identity parse: - R1 Clear the ambient CURSOR_AGENT marker on every Claude-pinned invocation in tests/fm-turnend-guard.test.sh, so the suite exercises Claude's repair wording when run from a Cursor primary. A regression case asserts the hermeticity rather than leaving it to convention. - R2 State one launch-boundary rule: every template-built non-Cursor worker clears CURSOR_AGENT, muse included. The raw-command escape hatch keeps its byte-identical passthrough contract. - R3 Record the worker-server identity through the same parse teardown re-checks, so a comm containing spaces no longer defeats the recycled-pid guard. Spawn previously read `$22` off the raw /proc line. - R4 Accept an exact cursor-agent name; require Cursor-specific executable or install-path evidence for MainThread, a bare interpreter, or the legacy alias. Worker-server discovery now binds the exact resolved executable, not a basename. - R5 Refuse a Cursor spawn on zellij, orca, and cmux before any endpoint, launch command, or task metadata exists: those backends expose no pane process tree, so the detached worker-server could never be recorded and would leak. - R6 Accept the legacy `agent` alias only after it proves itself Cursor, either structurally through Cursor's versioned install tree or through a bounded, fail-closed --help identity probe. A bare zero exit is never proof. The remote doctor reproduces the resolver rather than sourcing it, so its hash-verified bootstrap never pulls in an unverified helper; a test pins the two to the same verdicts, and the pinned doctor digest is refreshed. The Orca runtime probe now runs after the harness/backend refusals, so an impossible combination is reported as the configuration error it is. Ghost-gap handling, wake-chain bounds, recorded worker-server reaping, recycled-pid refusal, hooks validation, and the cursor-unverified busy posture are unchanged.
…n tree Three suites assemble their own bin/ directory for a real teardown or hook subprocess and list each sibling the script sources. bin/fm-cursor-lib.sh is a new one of those, so those fixtures failed at source time rather than on the behavior under test.
vipentti
force-pushed
the
fm/cursor-adapter-ship-4
branch
from
August 7, 2026 05:34
9ee19a5 to
91c4f5e
Compare
vipentti
marked this pull request as draft
August 7, 2026 05:59
…eue rules Behaviour-neutral cleanup of the Cursor adapter's duplicated logic. - fm_composer_export_env owns the per-harness composer env contract (including Cursor's "Add a follow-up" idle default, which an explicit caller-supplied regex still overrides), replacing the copies in fm-send.sh and fm-supervise-daemon.sh. - clamp_digits owns the leading-zero strip plus saturation shared by normalize_budget, normalize_counter, and fallback_total_from_loop_count in the Cursor turn-end shim. The length-before-value comparison, the exact ceilings, and each caller's own malformed-input answer are unchanged. - fm_composer_queued_submit_verdict owns the exhausted-retry rule (verified queueing harness plus an affirmatively busy pane reports delivered, everything else stays pending) for both the tmux and herdr submit loops. Each backend keeps its own busy probe, and the probe runs only for a harness whose answer can change the verdict. - Removes the fm_tmux_strip_ghost wrapper; callers and tests use fm_composer_strip_ghost directly. Adds coverage for the two new shared composer helpers, including that the busy exception never widens past opencode and cursor.
A1: fm-spawn.sh's kimi and muse resolvers duplicated the same command-v-to-absolute-path block; one absolute_command_path helper now owns it, and the single-line resolve_cursor_binary wrapper is gone in favour of calling fm_cursor_resolve_binary directly. bin/fm-remote-doctor.sh keeps its deliberate reproduction of the Cursor resolver: the remote entrypoint may run the doctor before the job worker exists, so its only identity proof on a git-less host is its pinned SHA-256, and its remaining absolute-path block cannot use the reproduced canonicalizer without resolving symlinks for every other harness. Unchanged, so the pinned digest still holds. A4: bin/fm-composer-lib.sh gains fm_composer_cursor_arrow_ok (bordered composer or cursor harness) and fm_composer_strip_prompt_glyph, and every arrow verdict and glyph strip in the classifier routes through them. All current verdicts preserved: an unscoped bare arrow stays unknown, a Cursor arrow with real text stays pending, a verified Cursor idle placeholder reads empty, and ghost-only unrelated bare rows stay unknown. The shared strip uses literal glyph prefixes rather than character-count wildcards, which is the only form correct outside a UTF-8 locale. A7: the three near-twin cursor wake-refire suites collapse into one parameterized case covering alternating static reasons, dynamic stale details, and process-event sequence details. Canonicalization, the ceiling diagnostic, state clearing, and the restarted chain all stay covered. A8: bin/fm-turnend-guard-cursor.sh's header restated the whole chain algorithm; it now states the contract and points at docs/supervision-protocols/cursor.md, which owns it. Safety-critical comments stay next to the implementation, and the doc's key list now names wake_reason. A9: the followup body is built with jq rather than hand-rolled backslash and quote escaping. The message text is unchanged, including its flattening to one line.
…R_AGENT The Cursor adapter makes CURSOR_AGENT win over other harness markers in fm-harness.sh, so the kimi, muse, and watcher-lock fixtures that pin a harness resolved to cursor whenever the suite ran from a Cursor session. Unset CURSOR_AGENT on those pinned invocations, matching the pattern the spawn and turn-end guard suites already use.
…dentity Gate Cursor-only reverse-video gap drops and Herdr bare-arrow structural admission on harness context, and classify Cursor processes from argv0 only so a later cursor-agent argument token cannot impersonate the harness.
…ID identity Round-2 review: keep established ❯/›/⟩ ghost-only composers empty like main, activate Cursor ghost_gap buffering only under FM_COMPOSER_HARNESS=cursor, and move generic process-reuse identity out of the Cursor lib.
…ety comment Final cleanup: exercise ghost_gap buffering under FM_COMPOSER_HARNESS=cursor for SGR27 and real-text-between-ghost preservation, and document that bare → is Cursor-scoped rather than empty either way.
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.
Overview
This PR adds first-class Cursor agent harness support to firstmate. Cursor becomes a verified adapter for crewmate and secondmate work: firstmate detects trusted Cursor processes and resolves installed executables, spawns agents, participates in session locking, supports tmux and Herdr endpoints, checks remote readiness and liveness, and safely contains and reaps detached worker-server processes.
It also adds tracked Cursor
stopandpreToolUsehooks with turn-end supervision, extends composer, ghost-text, busy-queue, and submission handling for Cursor's TUI, and documents and tests the behavior. This matters because Cursor's process identity, composer, hook protocol, and detached worker-server differ from existing harnesses; explicit adapter support lets firstmate supervise and clean up Cursor workers with fail-closed lifecycle behavior across local and remote work.The adapter scope covers crewmate and secondmate harnesses. Primary-mode Cursor integration is also present in this shipped head through
.cursor/hooks.json, thestop/preToolUseguard, and hook-owned turn-end supervision; no broader primary-mode feature set is claimed.What Changed
cursor-agentidentity or a proven legacy alias; unrelatedagentprocesses, bare interpreters, and unverified aliases fail closed.stopandpreToolUsehooks with Cursor-formatted deny responses, hook-owned turn-end supervision, continuation re-arming, bounded wake chains, and safe follow-up handling.→prompt,Add a follow-upidle state, ghost rendering, and Enter-while-busy queue behavior across tmux and Herdr.CURSOR_AGENT, deduplicate resolver and shared arrow/glyph predicates, parameterize wake-refire coverage, keep the Cursor shim contract beside its implementation, and build follow-up JSON withjq.How it was validated
fm-composer-lib,fm-composer-ghost,fm-turnend-guard,fm-remote-doctor,fm-spawn-dispatch-profile, Cursor harness detection, session locking, tmux/Herdr behavior, liveness, session start, busy submission, remote secondmates, and teardown.bin/fm-lint.shpassed.bin/fm-doc-audience-check.shpassed.929c4f43bd8c621d5513116430f3a2a2e39f64a6.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ **Rebase** - passed
✅ **Review** - completed
🔧 **Test** - 2 issues found → auto-fixed (2) ✅
✅ **Document** - passed
✅ **Lint** - passed
✅ **Push** - passed