Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/harness-adapters/references/harness/rovo.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ rovo launches BARE (`rovo run --yolo`, plus any `--model`/`--config-override` fl
2. **Typed pointer**: `Read the brief at <absolute-path> and follow it exactly.`, submitted through `fm_backend_send_text_submit` (the exact wording and mechanism kimi uses).
3. **Delivery gate** (`rovo_wait_for_delivery`): composer empty AND either the echoed pointer text (`Read the brief at`) has scrolled into view or rovo's `Context:` footer percentage has advanced off zero. rovo's real footer is `Context: <bar> N.N% NN.NK/NNNK` (e.g. `Context: ▎ 3.3% 30.1K/922K`); the delivery regex tolerates the bar glyph and arbitrary spacing but anchors to the digits before the `%`, so the always-nonzero denominator (`.../922K`) can never masquerade as usage.

A positional brief is dead-on-arrival: `rovo run --yolo "<brief>"` loads, never enters a working state, and drops back to an idle shell within about 10-15 seconds - confirmed independently four times over a raw PTY and once under real tmux 3.6a with the exact `fm-spawn.sh` send-keys shape. `--startup-receipt` cannot rescue that shape either: it requires "prompt-free interactive mode" (`Invalid value: --startup-receipt requires prompt-free interactive mode in a terminal`), so it cannot gate a launch that will have a message typed into it. The launch-then-send shape, by contrast, is confirmed live end to end (bare launch -> `Welcome to Rovo!` -> typed pointer -> `Rovo is thinking` for a real bash tool call -> clean `/exit`); see `../../../../docs/verification/rovo.md`.
A positional brief is dead-on-arrival: `rovo run --yolo "<brief>"` loads, never enters a working state, and drops back to an idle shell within about 10-15 seconds - confirmed independently four times over a raw PTY and once under real tmux 3.6a with `fm-spawn.sh`'s then-current send-keys shape (typing the full launch line; `fm-spawn.sh` now types a short line sourcing a launch file instead, see its header). `--startup-receipt` cannot rescue that shape either: it requires "prompt-free interactive mode" (`Invalid value: --startup-receipt requires prompt-free interactive mode in a terminal`), so it cannot gate a launch that will have a message typed into it. The launch-then-send shape, by contrast, is confirmed live end to end (bare launch -> `Welcome to Rovo!` -> typed pointer -> `Rovo is thinking` for a real bash tool call -> clean `/exit`); see `../../../../docs/verification/rovo.md`.
rovo leaves no worktree-resident artifact and no firstmate-owned sidecar at all, and has no readiness receipt or session-id to record.

## Composer ghost text: a known, unfixed gap
Expand Down
33 changes: 28 additions & 5 deletions bin/fm-spawn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,17 @@
# Before typing anything, a relaunch clears whatever the adopted shell still
# holds with Ctrl+C, so a previous launch that never landed - a partial line
# or an open quote - cannot swallow the replacement's.
# Launch file: the launch command is never typed whole. Typed text that
# arrives while the pane shell is not in its line editor - still starting, or
# running a pre-prompt hook such as mise's after each typed export - is held
# by the terminal's canonical input, which keeps only its first 1024 bytes on
# macOS and drops the rest along with the Enter behind it. So the command is
# written to a private launch.sh in the per-task temp root and the pane is
# typed only a short line sourcing it, which evaluates the command in the pane
# shell exactly as typing it would.
# Launch confirmation: a typed launch is never its own proof that an agent
# started. A line longer than the terminal's canonical-input limit (1024
# bytes on macOS) that arrives before the pane shell's line editor is running
# is cut short, which can leave the shell at a continuation prompt with no
# started. A launch line can still be cut short or garbled on its way into
# the pane, which can leave the shell at a continuation prompt with no
# agent at all. On a backend whose agent-state classifier proves a launched
# agent from the pane's own processes (bin/fm-backend.sh's
# fm_backend_launch_confirmable: tmux) every launch, fresh or relaunch,
Expand Down Expand Up @@ -3765,7 +3772,7 @@ spawn_clear_shell_input() {
}

spawn_type_launch() {
spawn_send_literal "$T" "$LAUNCH"
spawn_send_literal "$T" "$LAUNCH_LINE"
sleep 0.3
spawn_send_key "$T" Enter
}
Expand Down Expand Up @@ -4809,8 +4816,24 @@ if [ "$LAUNCH_ENV_ENABLED" = 1 ]; then
fi
LAUNCH="$LAUNCH_ENV_PREFIX /bin/sh -c $(shell_quote "$LAUNCH")"
fi
# The header's "Launch file" paragraph owns why only this short line is typed.
# The temp root must be this user's own real directory, since the pane shell
# runs whatever launch.sh holds.
if [ -L "$TASK_TMP" ] || [ ! -d "$TASK_TMP" ] || [ ! -O "$TASK_TMP" ]; then
echo "error: per-task temp root $TASK_TMP is not a directory owned by this user; refusing to write a launch file there; inspect window $T" >&2
exit 1
fi
LAUNCH_FILE="$TASK_TMP/launch.sh"
if ! LAUNCH_FILE_TMP=$(mktemp "$TASK_TMP/.launch.sh.XXXXXX") ||
! printf '%s\n' "$LAUNCH" >"$LAUNCH_FILE_TMP" ||
! mv -f "$LAUNCH_FILE_TMP" "$LAUNCH_FILE"; then
rm -f "${LAUNCH_FILE_TMP:-}" 2>/dev/null || true
echo "error: could not write the launch file $LAUNCH_FILE; inspect window $T" >&2
exit 1
fi
LAUNCH_LINE=". $(shell_quote "$LAUNCH_FILE")"
sleep 0.3
spawn_send_literal "$T" "$LAUNCH"
spawn_send_literal "$T" "$LAUNCH_LINE"
sleep 0.3
if [ "${HERDR_PROJECTED:-0}" -eq 1 ]; then
HERDR_PROJECTION_ABORT_CLEANUP=0
Expand Down
2 changes: 1 addition & 1 deletion docs/verification/rovo.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Each gate fails the spawn loudly (a `failed:` line in the task status file) if i

### Why not a positional brief

A positional brief is dead-on-arrival. `rovo run --yolo "<brief>"` loads a spinner, never enters a working state, prints no reply, and drops back to a bare idle shell prompt within about 10-15 seconds. This was reproduced independently four times over a raw PTY (varying `TERM`, window size, workspace, and 60-150s windows) and once more under real tmux 3.6a driven with the exact `fm-spawn.sh` send-keys shape (new window, `send-keys -l` the full launch line, then `Enter`). `--startup-receipt` cannot rescue that shape either - it is rejected before start alongside any message:
A positional brief is dead-on-arrival. `rovo run --yolo "<brief>"` loads a spinner, never enters a working state, prints no reply, and drops back to a bare idle shell prompt within about 10-15 seconds. This was reproduced independently four times over a raw PTY (varying `TERM`, window size, workspace, and 60-150s windows) and once more under real tmux 3.6a driven with `fm-spawn.sh`'s then-current send-keys shape (new window, `send-keys -l` the full launch line, then `Enter`; `fm-spawn.sh` now types a short line sourcing a launch file instead, see its header). `--startup-receipt` cannot rescue that shape either - it is rejected before start alongside any message:

```
$ rovo run --startup-receipt receipt.json --yolo "Reply with PONG"
Expand Down
31 changes: 30 additions & 1 deletion tests/fixtures.sh
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ case "${1:-}" in
prev=
for a in "$@"; do
if [ "$prev" = "-l" ]; then
printf '%s\n' "$a" >> "$FM_FAKE_LAUNCH_LOG"
printf '%s\n' "$(fm_fake_sourced_launch "$a")" >> "$FM_FAKE_LAUNCH_LOG"
fi
prev=$a
done
Expand All @@ -144,9 +144,38 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fakebin/tmux"
chmod +x "$fakebin/tmux"
}

# fm_test_fake_sourced_launch_fn <fake-script>
# fm-spawn.sh types a launch as a short line sourcing a launch file, which the
# pane shell evaluates as the command the file holds. Inserts, right after the
# fake's shebang line, fm_fake_sourced_launch <literal>: it prints the file's
# command for such a line and the literal unchanged otherwise, so a fake's
# launch log keeps reading as the command the agent was started with.
fm_test_fake_sourced_launch_fn() {
local script=$1 tmp
tmp=$(mktemp "$script.XXXXXX") || return 1
{
head -n 1 "$script"
cat <<'SH'
fm_fake_sourced_launch() {
local f
case "$1" in
". '"*"'")
f=${1#". '"}
f=${f%"'"}
if [ -f "$f" ]; then cat "$f"; return 0; fi
;;
esac
printf '%s' "$1"
}
SH
tail -n +2 "$script"
} > "$tmp" && mv "$tmp" "$script"
}

# fm_test_fake_tmux_send <fakebin>
# Send-world tmux: logs send-keys -l payloads to FM_SEND_LOG, reports a numeric
# cursor_y, and renders an empty bordered composer so the submit path reads
Expand Down
7 changes: 4 additions & 3 deletions tests/fm-agy-harness.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
# and nothing short of that shared proof flips an agy pane to agent-free.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"

# bin/fm-harness.sh checks verified ENV markers before ancestry. A suite run
# from inside another harness inherits those markers, which outrank the fake
Expand Down Expand Up @@ -498,7 +498,7 @@ case "${1:-}" in
literal=
prev=
for arg in "$@"; do
if [ "$prev" = -l ]; then literal=$arg; break; fi
if [ "$prev" = -l ]; then literal=$(fm_fake_sourced_launch "$arg"); break; fi
prev=$arg
done
if [ -n "$literal" ]; then
Expand Down Expand Up @@ -536,6 +536,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fakebin/tmux"
chmod +x "$fakebin/tmux"
cat > "$fakebin/agy" <<'SH'
#!/usr/bin/env bash
Expand Down
12 changes: 9 additions & 3 deletions tests/fm-backend-orca.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# terminal adapter primitives in bin/backends/orca.sh.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"

TMP_ROOT=$(fm_test_tmproot fm-backend-orca-tests)
# A claude spawn writes workspace trust into the launching user's own store,
Expand Down Expand Up @@ -39,7 +39,12 @@ COUNT_FILE="$RESP/.count"
next=$(( $(cat "$COUNT_FILE" 2>/dev/null || echo 0) + 1 ))
{
printf 'orca'
for a in "$@"; do printf '\x1f%s' "$a"; done
prev=
for a in "$@"; do
if [ "$prev" = --text ]; then a=$(fm_fake_sourced_launch "$a"); fi
printf '\x1f%s' "$a"
prev=$a
done
printf '\n'
} >> "$LOG"
if [ "${1:-}" = status ] && [ "${FM_ORCA_STATUS_RESPONSE:-ready}" != sequence ]; then
Expand All @@ -54,6 +59,7 @@ fi
[ -f "$RESP/$n.out" ] && cat "$RESP/$n.out"
exit 0
SH
fm_test_fake_sourced_launch_fn "$fb/orca"
chmod +x "$fb/orca"
fm_fake_harness_clis "$fb"
printf '%s\n' "$fb"
Expand Down
6 changes: 4 additions & 2 deletions tests/fm-control-relaunch.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
# agent exited.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"
# shellcheck source=/dev/null
. "$ROOT/bin/fm-control-lib.sh"
# shellcheck source=/dev/null
Expand Down Expand Up @@ -72,6 +72,7 @@ case "${1:-}" in
done
payload=${1:-}
if [ "$literal" = 1 ]; then
payload=$(fm_fake_sourced_launch "$payload")
printf '%s\n' "$payload" >> "$D/literal"
case "$payload" in
/exit|/quit)
Expand Down Expand Up @@ -124,6 +125,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fb/tmux"
chmod +x "$fb/tmux"
cat > "$fb/sleep" <<'SH'
#!/usr/bin/env bash
Expand Down
6 changes: 4 additions & 2 deletions tests/fm-control.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
# while fm-send's marking of the same task is untouched.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"
# shellcheck source=/dev/null
. "$ROOT/bin/fm-control-lib.sh"
# shellcheck source=/dev/null
Expand Down Expand Up @@ -93,6 +93,7 @@ case "${1:-}" in
done
payload=${1:-}
if [ "$literal" = 1 ]; then
payload=$(fm_fake_sourced_launch "$payload")
printf '%s\n' "$payload" >> "$D/literal"
if [ -z "${FM_FAKE_NEVER_DIES:-}" ] \
&& { [ "$payload" = /exit ] || [ "$payload" = /quit ]; }; then
Expand Down Expand Up @@ -134,6 +135,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fb/tmux"
chmod +x "$fb/tmux"
cat > "$fb/sleep" <<'SH'
#!/usr/bin/env bash
Expand Down
7 changes: 4 additions & 3 deletions tests/fm-kimi-harness.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Behavior tests for the verified Kimi Code CLI crewmate adapter.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"

# bin/fm-harness.sh answers from environment markers and process ancestry. A
# suite run from inside Cursor, Claude, Pi, or Grok inherits those markers and
Expand Down Expand Up @@ -79,7 +79,7 @@ case "${1:-}" in
prev=
literal=
for arg in "$@"; do
if [ "$prev" = -l ]; then literal=$arg; break; fi
if [ "$prev" = -l ]; then literal=$(fm_fake_sourced_launch "$arg"); break; fi
prev=$arg
done
if [ -n "$literal" ]; then
Expand Down Expand Up @@ -139,6 +139,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fakebin/tmux"
chmod +x "$fakebin/tmux"
fm_fake_exit0 "$fakebin" treehouse gh-axi gh
fm_fake_exit0 "$fakebin" kimi
Expand Down
6 changes: 4 additions & 2 deletions tests/fm-muse-harness.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
# fixture without it would let a naive implementation pass.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"

# bin/fm-harness.sh checks verified ENV markers before ancestry. Muse is
# markerless, so an inherited Cursor/Claude/Pi/Grok marker would outrank the
Expand Down Expand Up @@ -96,6 +96,7 @@ case "${1:-}" in
prev=
for arg in "$@"; do
if [ "$prev" = -l ]; then
arg=$(fm_fake_sourced_launch "$arg")
printf '%s\n' "$arg" >> "$FM_FAKE_LAUNCH_LOG"
if [ "${FM_FAKE_EXECUTE_MUSE_LAUNCH:-}" = 1 ]; then
case "$arg" in
Expand All @@ -111,6 +112,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fakebin/tmux"
chmod +x "$fakebin/tmux"
cp "$(command -v bash)" "$fakebin/muse-bin-test-version"
cat > "$fakebin/muse" <<'SH'
Expand Down
7 changes: 4 additions & 3 deletions tests/fm-rovo-harness.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Behavior tests for the verified Rovo CLI crewmate/scout adapter.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"

# bin/fm-harness.sh checks verified ENV markers before ancestry, but that
# ordering settles the marker layer only: a structural (comm-strength)
Expand Down Expand Up @@ -75,7 +75,7 @@ case "${1:-}" in
prev=
literal=
for arg in "$@"; do
if [ "$prev" = -l ]; then literal=$arg; break; fi
if [ "$prev" = -l ]; then literal=$(fm_fake_sourced_launch "$arg"); break; fi
prev=$arg
done
if [ -n "$literal" ]; then
Expand Down Expand Up @@ -130,6 +130,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fakebin/tmux"
chmod +x "$fakebin/tmux"
fm_fake_exit0 "$fakebin" treehouse gh-axi gh
fm_fake_exit0 "$fakebin" rovo
Expand Down
7 changes: 4 additions & 3 deletions tests/fm-secondmate-harness.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
# flags still win.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"
# shellcheck source=/dev/null
. "$ROOT/bin/fm-ff-lib.sh"
# shellcheck source=/dev/null
Expand Down Expand Up @@ -692,7 +692,7 @@ case "${1:-}" in
prev=
for a in "$@"; do
if [ "$prev" = "-l" ]; then
printf '%s\n' "$a" >> "$FM_FAKE_LAUNCH_LOG"
printf '%s\n' "$(fm_fake_sourced_launch "$a")" >> "$FM_FAKE_LAUNCH_LOG"
fi
prev=$a
done
Expand All @@ -702,6 +702,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fakebin/tmux"
chmod +x "$fakebin/tmux"
fm_fake_exit0 "$fakebin" pi
# BASE_PATH deliberately omits the developer's node, which the trust
Expand Down
6 changes: 4 additions & 2 deletions tests/fm-secondmate-restart.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
# its agent left running.
set -u

# shellcheck source=tests/lib.sh
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
# shellcheck source=tests/fixtures.sh
. "$(dirname "${BASH_SOURCE[0]}")/fixtures.sh"

RESTART="$ROOT/bin/fm-secondmate-restart.sh"

Expand Down Expand Up @@ -63,6 +63,7 @@ case "${1:-}" in
done
payload=${1:-}
if [ "$literal" = 1 ]; then
payload=$(fm_fake_sourced_launch "$payload")
printf '%s\n' "$payload" >> "$D/literal"
case "$payload" in
/exit|/quit)
Expand Down Expand Up @@ -115,6 +116,7 @@ case "${1:-}" in
esac
exit 0
SH
fm_test_fake_sourced_launch_fn "$fb/tmux"
chmod +x "$fb/tmux"
cat > "$fb/sleep" <<'SH'
#!/usr/bin/env bash
Expand Down
Loading
Loading