Skip to content
Open
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
15 changes: 15 additions & 0 deletions bin/fm-spawn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2897,6 +2897,21 @@ case "$ARG3" in
;;
esac
done
# A named adapter template embeds the launch-brief arg itself; a raw command
# has no such placeholder and would start the agent at an empty prompt.
# Append it after the caller's own argv. Kimi and rovo reject a positional
# prompt and get their brief pointer after their readiness gate, and a command
# that already names __BRIEF__ is left alone.
case "$HARNESS" in
kimi* | rovo*) : ;;
*)
# shellcheck disable=SC2016 # literal text, expanded by the launched shell
case "$LAUNCH" in
*__BRIEF__*) : ;;
*) LAUNCH=$LAUNCH' "$(__OPINPUT__ encode launch-brief < __BRIEF__)"' ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Brief reaches the wrong command

Appending to the whole shell string does not always pass the brief to the agent. With custom-agent --flag; cleanup, the brief becomes an argument to cleanup, while the agent still starts without a prompt. With custom-agent --flag;, the shell later tries to run the encoded brief as a command. Compound raw commands are already exercised by this test file.

Handle these command shapes explicitly, or reject them with guidance to place __BRIEF__ in the agent command.

esac
;;
esac
;;
'')
# No explicit harness: resolve from config. A secondmate AGENT launches on the
Expand Down
25 changes: 22 additions & 3 deletions tests/fm-spawn-dispatch-profile.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ test_launch_preserves_inherited_git_config() {
rec=$(make_spawn_case "$id" codex "$id")
read_case_record "$rec"
out=$(run_ship_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" \
"$id" "$PROJ_DIR" --harness 'echo WORKER-RAN >&2; git config --get-all credential.helper; git config --get core.hooksPath')
"$id" "$PROJ_DIR" --harness 'echo WORKER-RAN >&2; git config --get-all credential.helper; git config --get core.hooksPath; :')
status=$?
expect_code 0 "$status" "Git configuration probe should spawn: $out"
launch=$(cat "$LAUNCH_LOG")
Expand Down Expand Up @@ -597,18 +597,36 @@ test_active_dispatch_profile_allows_raw_launch_command() {
# The unverified-adapter escape hatch is still an agent this fleet launched,
# so it carries the compact-adviser floor and the AI-trailer strip; nothing
# else may rewrite the captain's own command.
[ "$launch" = "export COMPACT_ADVISER_DISABLE=1; $(task_inbox_export "$HOME_DIR" "$id")$(fm_test_launch_git_prefix "$launch")custom-agent --flag" ] || fail "raw launch command changed"$'\n'"actual: $launch"
expected="export COMPACT_ADVISER_DISABLE=1; $(task_inbox_export "$HOME_DIR" "$id")$(fm_test_launch_git_prefix "$launch")custom-agent --flag \"\$('$ROOT/bin/fm-operational-input.sh' encode launch-brief < '$HOME_DIR/data/$id/launch-brief.md')\""
[ "$launch" = "$expected" ] || fail "raw launch command changed"$'\n'"actual: $launch"
pass "active crew-dispatch profile allows the raw launch-command escape hatch"
}

test_raw_rovo_launch_gets_no_positional_brief() {
local rec id out status launch
id=raw-rovo-nobrief-z15
rec=$(make_spawn_case raw-rovo-nobrief claude "$id")
read_case_record "$rec"

out=$(run_ship_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" \
"$id" "$PROJ_DIR" "rovo run --yolo")
status=$?
expect_code 1 "$status" "raw rovo launch should reach rovo's readiness gate, which the fake pane never satisfies"
assert_contains "$out" "rovo did not show a verified ready signal" "raw rovo launch did not take the rovo readiness path"
launch=$(cat "$LAUNCH_LOG")
assert_contains "$launch" "rovo run --yolo" "raw rovo launch command was not emitted"
assert_not_contains "$launch" "launch-brief" "raw rovo launch got a positional brief argument"
pass "a raw rovo launch is not given the positional launch-brief argument"
}

test_chained_raw_launch_strips_ai_trailer_in_every_step() {
local rec id out status launch body
id=chained-raw-z15
rec=$(make_spawn_case chained-raw claude "$id")
read_case_record "$rec"

out=$(run_ship_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" \
"$id" "$PROJ_DIR" "cd . && git commit -q --allow-empty --trailer 'Co-authored-by: Cursor <cursoragent@cursor.com>' -m 'fix: chained raw launch'")
"$id" "$PROJ_DIR" "cd . && git commit -q --allow-empty --trailer 'Co-authored-by: Cursor <cursoragent@cursor.com>' -m 'fix: chained raw launch' && true")
status=$?
expect_code 0 "$status" "chained raw launch should spawn: $out"
launch=$(cat "$LAUNCH_LOG")
Expand Down Expand Up @@ -2727,6 +2745,7 @@ test_active_dispatch_profile_requires_explicit_harness_for_scout
test_active_dispatch_profile_allows_explicit_harness
test_active_dispatch_profile_allows_positional_harness
test_active_dispatch_profile_allows_raw_launch_command
test_raw_rovo_launch_gets_no_positional_brief
test_chained_raw_launch_strips_ai_trailer_in_every_step
test_claude_threads_model_and_effort
test_codex_threads_model_and_effort
Expand Down
Loading