Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
09d7ae4
fix: isolate Codex crewmate MCP config
Jul 16, 2026
92c35fd
no-mistakes(review): Harden Codex home cleanup failures
Jul 16, 2026
2851b78
no-mistakes(review): Harden isolated Codex crewmate launches
Jul 16, 2026
b95225f
fix: scope Codex trust to worktree
Jul 16, 2026
1871b49
no-mistakes(review): Harden Codex crewmate launch isolation
Jul 16, 2026
eb101a1
no-mistakes(review): Record failed Codex endpoint cleanup
Jul 16, 2026
a35b2ea
no-mistakes(review): Harden case-insensitive Codex launch isolation
Jul 16, 2026
d913a16
no-mistakes(review): Harden Codex raw launch isolation
Jul 16, 2026
ef04d6f
no-mistakes(review): Harden Codex spawn isolation
Jul 16, 2026
7d80b3c
no-mistakes(review): Harden Codex launch isolation cleanup
Jul 16, 2026
9633114
no-mistakes(review): Harden Codex wrappers and Orca cleanup metadata
Jul 16, 2026
3320338
no-mistakes(review): Harden Codex isolation and cleanup
Jul 16, 2026
adf0546
no-mistakes(review): Captain: Harden Codex spawn isolation
Jul 16, 2026
52d5fc2
no-mistakes(review): Harden Codex launch isolation
Jul 16, 2026
bd01a65
no-mistakes(review): Captain: Harden Codex launch isolation
Jul 16, 2026
7a8a51c
no-mistakes(review): Captain: harden Codex activation and backend cle…
Jul 16, 2026
6d3ca40
no-mistakes(review): Harden Codex launch isolation
Jul 16, 2026
bae5c24
no-mistakes(review): Captain: preserve raw custom shell launches
Jul 16, 2026
8053a94
no-mistakes(review): Harden Codex isolation and Zellij cleanup
Jul 16, 2026
3ecb7fe
no-mistakes(review): Confirm strict Zellij cleanup closures
Jul 16, 2026
6576097
no-mistakes(review): Harden Codex activation result isolation
Jul 16, 2026
d03756e
no-mistakes(review): Harden Codex launch isolation
Jul 16, 2026
0e3a2b6
fix: harden failed spawn teardown
Jul 16, 2026
f592c35
fix: harden Codex launch cleanup
Jul 16, 2026
770656f
no-mistakes(review): Harden indirect Codex launch isolation
Jul 16, 2026
c66daf8
no-mistakes(review): Harden Codex activation and builtin dispatch
Jul 16, 2026
92c46cf
no-mistakes(review): Harden Orca cleanup and raw launch parsing
Jul 16, 2026
c92ae7f
no-mistakes(review): Confirm Orca terminal absence before cleanup
Jul 16, 2026
e509647
no-mistakes(test): Disable external review diff drivers
Jul 16, 2026
1566040
no-mistakes(test): Fix spawn isolation test fixture
Jul 17, 2026
7f6c649
no-mistakes(review): Respect explicit CODEX_HOME overrides
Jul 17, 2026
a838c7e
no-mistakes(document): Document Codex crewmate isolation
Jul 17, 2026
2d1ea00
no-mistakes(lint): Fix ShellCheck lint diagnostics
Jul 17, 2026
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
4 changes: 4 additions & 0 deletions .agents/skills/harness-adapters/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ Claude Code's primary watcher protocol is the lowest-friction path: run `bin/fm-

## codex (VERIFIED 2026-06-11, codex-cli 0.139.0)

For ship and scout launches, `fm-spawn.sh` refreshes a firstmate-owned isolated `CODEX_HOME` with no MCP servers or plugins before starting Codex.
It copies only the captain home's current Codex authentication and model catalog, so the captain's `~/.codex` configuration remains untouched.
Secondmate Codex launches intentionally retain their existing home behavior.

| Fact | Value |
|---|---|
| Busy-pane signature | `esc to interrupt` (shown as `• Working (Xs • esc to interrupt)`) |
Expand Down
14 changes: 11 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ config/wedge-alarm optional away-mode wedge-alarm active-alert directives; LOCA
config/x-mode.env generated X-mode watcher cadence; LOCAL, gitignored; source before arming watcher when present
data/ personal fleet records; LOCAL, gitignored as a whole
backlog.md task queue, dependencies, history
captain.md captain's personal preferences and working style; LOCAL, gitignored, canonical even if harness memory mirrors it, and updated with inspect-then-update
captain.md this home's domain-local captain preferences and working style; LOCAL, gitignored, canonical even if harness memory mirrors it, and updated with inspect-then-update
captain-shared.md main-authoritative shared captain preferences propagated read-only to secondmate homes; LOCAL, gitignored, owned by secondmate-provisioning
codex-crewmate/ task-private Codex homes for ship and scout launches; LOCAL, gitignored, owned by fm-codex-home.py and removed by teardown
learnings.md fleet-local operational facts and gotchas; LOCAL, gitignored; dated, evidence-backed, curated, and updated with inspect-then-update - rewrite and prune rather than append forever, the same contract as captain.md; created lazily, absent until this home has a learning to store
projects.md thin fleet navigation registry; firstmate-private, parsed by fm-project-mode.sh (section 6)
secondmates.md secondmate routing table; firstmate-private, maintained by fm-home-seed.sh (section 6)
Expand All @@ -95,8 +97,14 @@ state/ volatile runtime signals; gitignored
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
<id>.turn-ended touched by turn-end hooks
<id>.grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown
<id>.meta written by fm-spawn: window=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, appends pr= and GitHub's pr_head= when available; fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.check.sh optional slow poll you write per task (e.g. merged-PR check)
<id>.meta written by fm-spawn: window=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; Codex ship/scout tasks also record codex_crewmate_home=; kind=secondmate also records home= and projects=; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and GitHub's pr_head= when available; fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified X shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution
<id>.check-trust private content binding created by fm-check-register.sh for an intentional custom check
<id>.pr-poll private validated data sidecar for the byte-static PR merge poll
<id>.pr-poll-registration private transactional provenance record binding the task, canonical metadata identity, sidecar, and static poll publication
.pr-check-quarantine/ private non-runnable storage for checks neutralized by the non-executing migration
.pr-check-migration.log private per-task outcomes distinguishing rebuilt or canonically registered replacement polls, quarantined unarmed polls, and incomplete migrations
.pr-check-migration-scan-v1 private marker proving the non-executing scan disabled every unsafe legacy check; .pr-check-migration-v1 separately records completed private repairs
x-watch.check.sh generated X-mode relay poll shim; present only when opted in (section 14)
x-inbox/ generated X-mode pending mention payloads; fmx-respond drains it (section 14)
x-context/ generated X-mode durable per-request reply context (platform/budget), keyed by request_id; survives inbox cleanup so a delayed follow-up recovers the original platform (section 14; bin/fm-x-lib.sh)
Expand Down
14 changes: 9 additions & 5 deletions bin/backends/cmux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -620,8 +620,7 @@ fm_backend_cmux_window_of_workspace() { # <workspace_id> -> "<window_id> <count
done < <(printf '%s' "$wins" | jq -r '.[]? | .id' 2>/dev/null)
}

# fm_backend_cmux_kill: remove the task's whole workspace, best-effort (mirrors
# every other backend's `kill` `|| true` contract). A cmux task owns one
# fm_backend_cmux_kill: remove the task's whole workspace. A cmux task owns one
# workspace, so teardown reclaims that workspace and all of its surfaces.
#
# The selected-workspace teardown bug (docs/cmux-backend.md "Closing the last
Expand All @@ -639,7 +638,12 @@ fm_backend_cmux_window_of_workspace() { # <workspace_id> -> "<window_id> <count
fm_backend_cmux_kill() { # <target> [unused] [expected-label]
local expected_label=${3:-} wsid wininfo win count
if [ -n "$expected_label" ]; then
fm_backend_cmux_target_ready "$1" "$expected_label" || return 0
if ! fm_backend_cmux_target_ready "$1" "$expected_label"; then
[ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ] && return 1
return 0
fi
elif [ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ]; then
fm_backend_cmux_target_ready "$1" || return 1
else
fm_backend_cmux_parse_target "$1" || return 0
fi
Expand All @@ -648,9 +652,9 @@ fm_backend_cmux_kill() { # <target> [unused] [expected-label]
win=${wininfo%% *}
count=${wininfo##* }
if [ -n "$win" ] && [ "$count" = 1 ]; then
fm_backend_cmux_cli new-workspace --window "$win" --focus false --id-format uuids >/dev/null 2>&1 || true
fm_backend_cmux_cli new-workspace --window "$win" --focus false --id-format uuids >/dev/null 2>&1 || [ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ] || return 1
fi
fm_backend_cmux_cli close-workspace --workspace "$wsid" >/dev/null 2>&1 || true
fm_backend_cmux_cli close-workspace --workspace "$wsid" >/dev/null 2>&1 || [ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ]
}

# fm_backend_cmux_list_live: recovery/orphan discovery. Lists every workspace
Expand Down
10 changes: 6 additions & 4 deletions bin/backends/herdr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -875,12 +875,14 @@ fm_backend_herdr_send_text_submit() { # <target> <text> <retries> <enter-sleep>
done
}

# fm_backend_herdr_kill: remove the task's pane, best-effort (mirrors
# tmux-kill-window's `|| true` contract). Verified: closing a tab's only pane
# fm_backend_herdr_kill: remove the task's pane. Verified: closing a tab's only pane
# closes the tab too, so a separate tab close is unnecessary.
fm_backend_herdr_kill() { # <target>
fm_backend_herdr_target_ready "$1" || return 0
fm_backend_herdr_cli "$FM_BACKEND_HERDR_SESSION" pane close "$FM_BACKEND_HERDR_PANE" >/dev/null 2>&1 || true
if ! fm_backend_herdr_target_ready "$1"; then
[ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ] && return 0
return 1
fi
fm_backend_herdr_cli "$FM_BACKEND_HERDR_SESSION" pane close "$FM_BACKEND_HERDR_PANE" >/dev/null 2>&1 || [ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ]
}

# fm_backend_herdr_classify_agent_status: map a raw `agent get` agent_status
Expand Down
8 changes: 6 additions & 2 deletions bin/backends/orca.sh
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,10 @@ fm_backend_orca_send_text_submit() { # <terminal-id> <text> <retries> <enter-sl
}

fm_backend_orca_kill() { # <terminal-id>
fm_backend_orca_tool_check || return 0
orca terminal close --terminal "$1" --json >/dev/null 2>&1 || true
fm_backend_orca_tool_check || [ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ] || return 1
if [ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ]; then
fm_backend_orca_run_json orca terminal close --terminal "$1" --json
else
orca terminal close --terminal "$1" --json >/dev/null 2>&1 || true
fi
}
5 changes: 2 additions & 3 deletions bin/backends/tmux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,9 @@ fm_backend_tmux_send_literal() { # <target> <text>
tmux send-keys -t "$1" -l "$2"
}

# fm_backend_tmux_kill: remove the task's window, best-effort. Mirrors
# fm-teardown.sh's `tmux kill-window -t "$T" 2>/dev/null || true`.
# fm_backend_tmux_kill: remove the task's window.
fm_backend_tmux_kill() { # <target>
tmux kill-window -t "$1" 2>/dev/null || true
tmux kill-window -t "$1" 2>/dev/null || [ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ]
}

# fm_backend_tmux_current_command: <target>'s live foreground process name -
Expand Down
86 changes: 72 additions & 14 deletions bin/backends/zellij.sh
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,10 @@ fm_backend_zellij_pane_for_tab() { # <session> <tab_id>
# target string; the tab id is looked up fresh rather than trusted stale,
# mirroring herdr's label-based, never-trust-a-stored-id recovery posture).
fm_backend_zellij_tab_for_pane() { # <session> <pane_id>
local session=$1 pane_id=$2
fm_backend_zellij_cli "$session" action list-panes --json 2>/dev/null \
local session=$1 pane_id=$2 panes
panes=$(fm_backend_zellij_cli "$session" action list-panes --json 2>/dev/null) || return 1
printf '%s' "$panes" | jq -e 'type == "array"' >/dev/null 2>&1 || return 1
printf '%s' "$panes" \
| jq -r --argjson p "$pane_id" '.[]? | select(.id == $p and .is_plugin == false) | .tab_id' 2>/dev/null | head -1
}

Expand Down Expand Up @@ -299,7 +301,8 @@ fm_backend_zellij_pane_exists() { # <session> <pane_id>
fm_backend_zellij_tab_matches_label() { # <session> <tab_id> <label>
local session=$1 tab_id=$2 label=$3 scoped tabs count
scoped=$(fm_backend_zellij_scoped_title "$label")
tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null)
tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null) || return 2
printf '%s' "$tabs" | jq -e 'type == "array"' >/dev/null 2>&1 || return 2
printf '%s' "$tabs" | jq -e --argjson t "$tab_id" --arg want "$scoped" \
'[.[]? | select(.tab_id == $t and .name == $want)] | length > 0' >/dev/null 2>&1 && return 0
printf '%s' "$tabs" | jq -e --argjson t "$tab_id" --arg want "$label" \
Expand All @@ -308,6 +311,22 @@ fm_backend_zellij_tab_matches_label() { # <session> <tab_id> <label>
[ "$count" = "1" ]
}

fm_backend_zellij_tab_absent() { # <session> <tab_id>
local session=$1 tab_id=$2 tabs
tabs=$(fm_backend_zellij_cli "$session" action list-tabs --json 2>/dev/null) || return 1
printf '%s' "$tabs" | jq -e 'type == "array"' >/dev/null 2>&1 || return 1
printf '%s' "$tabs" | jq -e --argjson t "$tab_id" \
'[.[]? | select(.tab_id == $t)] | length == 0' >/dev/null 2>&1
}

fm_backend_zellij_pane_absent() { # <session> <pane_id>
local session=$1 pane_id=$2 panes
panes=$(fm_backend_zellij_cli "$session" action list-panes --json 2>/dev/null) || return 1
printf '%s' "$panes" | jq -e 'type == "array"' >/dev/null 2>&1 || return 1
printf '%s' "$panes" | jq -e --argjson p "$pane_id" \
'[.[]? | select(.id == $p)] | length == 0' >/dev/null 2>&1
}

# fm_backend_zellij_create_task: create the task's tab (one terminal pane) in
# <session>, refusing an existing <label>. Zellij does NOT enforce tab-name
# uniqueness itself (verified: two tabs can share a name), so the duplicate
Expand Down Expand Up @@ -521,8 +540,7 @@ fm_backend_zellij_send_text_submit() { # <target> <text> <retries> <enter-sleep
done
}

# fm_backend_zellij_kill: remove the task's tab, best-effort (mirrors
# tmux-kill-window's/herdr-pane-close's `|| true` contract). Verified: unlike
# fm_backend_zellij_kill: remove the task's tab. Verified: unlike
# herdr, closing a zellij tab's only PANE does NOT close the tab itself (an
# empty tab survives in list-tabs); `close-tab-by-id` on a live tab DOES
# cleanly remove both the pane and the tab in one call, verified to need no
Expand All @@ -531,27 +549,67 @@ fm_backend_zellij_send_text_submit() { # <target> <text> <retries> <enter-sleep
# passes the recorded tab id and expected tab label for already-empty ghost
# tabs. Any tab id is verified against the expected label when one is provided.
fm_backend_zellij_kill() { # <target> [tab_id] [expected_label]
fm_backend_zellij_parse_target "$1" || return 0
fm_backend_zellij_session_exists "$FM_BACKEND_ZELLIJ_SESSION" || return 0
local tab_id fallback_tab_id=${2:-} expected_label=${3:-}
tab_id=$(fm_backend_zellij_tab_for_pane "$FM_BACKEND_ZELLIJ_SESSION" "$FM_BACKEND_ZELLIJ_PANE" 2>/dev/null)
if [ -n "$tab_id" ] && [ -n "$expected_label" ] && ! fm_backend_zellij_tab_matches_label "$FM_BACKEND_ZELLIJ_SESSION" "$tab_id" "$expected_label"; then
tab_id=
if ! fm_backend_zellij_parse_target "$1"; then
[ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ] && return 0
return 1
fi
if ! fm_backend_zellij_session_exists "$FM_BACKEND_ZELLIJ_SESSION"; then
[ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ] && return 0
return 1
fi
local tab_id query_status label_status fallback_tab_id=${2:-} expected_label=${3:-}
if tab_id=$(fm_backend_zellij_tab_for_pane "$FM_BACKEND_ZELLIJ_SESSION" "$FM_BACKEND_ZELLIJ_PANE" 2>/dev/null); then
query_status=0
else
query_status=$?
fi
if [ "$query_status" -ne 0 ] && [ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ]; then
return 1
fi
if [ -n "$tab_id" ] && [ -n "$expected_label" ]; then
if fm_backend_zellij_tab_matches_label "$FM_BACKEND_ZELLIJ_SESSION" "$tab_id" "$expected_label"; then
label_status=0
else
label_status=$?
fi
if [ "$label_status" -ne 0 ]; then
if [ "$label_status" -eq 2 ] && [ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ]; then
return 1
fi
tab_id=
fi
fi
case "$fallback_tab_id" in
''|*[!0-9]*) ;;
*)
if [ -z "$tab_id" ]; then
if [ -z "$expected_label" ] || fm_backend_zellij_tab_matches_label "$FM_BACKEND_ZELLIJ_SESSION" "$fallback_tab_id" "$expected_label"; then
if [ -z "$expected_label" ]; then
tab_id=$fallback_tab_id
else
if fm_backend_zellij_tab_matches_label "$FM_BACKEND_ZELLIJ_SESSION" "$fallback_tab_id" "$expected_label"; then
label_status=0
else
label_status=$?
fi
if [ "$label_status" -eq 0 ]; then
tab_id=$fallback_tab_id
elif [ "$label_status" -eq 2 ] && [ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ]; then
return 1
fi
fi
fi
;;
esac
if [ -n "$tab_id" ]; then
fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action close-tab-by-id "$tab_id" >/dev/null 2>&1 || true
fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action close-tab-by-id "$tab_id" >/dev/null 2>&1 || [ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ]
if [ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ]; then
fm_backend_zellij_tab_absent "$FM_BACKEND_ZELLIJ_SESSION" "$tab_id" || return 1
fi
elif [ -z "$expected_label" ]; then
fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action close-pane --pane-id "$FM_BACKEND_ZELLIJ_PANE" >/dev/null 2>&1 || true
fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action close-pane --pane-id "$FM_BACKEND_ZELLIJ_PANE" >/dev/null 2>&1 || [ "${FM_BACKEND_KILL_STRICT:-0}" != 1 ]
if [ "${FM_BACKEND_KILL_STRICT:-0}" = 1 ]; then
fm_backend_zellij_pane_absent "$FM_BACKEND_ZELLIJ_SESSION" "$FM_BACKEND_ZELLIJ_PANE" || return 1
fi
fi
}

Expand Down
84 changes: 81 additions & 3 deletions bin/fm-backend.sh
Original file line number Diff line number Diff line change
Expand Up @@ -564,9 +564,8 @@ fm_backend_send_text_submit() { # <backend> <target> <text> <retries> <enter-sl
esac
}

# fm_backend_kill: remove the task's session endpoint (best-effort; a
# nonexistent/already-gone target is not an error - callers already swallow
# failures here exactly as the inline `tmux kill-window ... || true` did).
# fm_backend_kill: remove the task's session endpoint (best-effort by default).
# Set FM_BACKEND_KILL_STRICT=1 when an allocation failure must be recorded if a backend close fails.
fm_backend_kill() { # <backend> <target>
local backend=$1
shift
Expand Down Expand Up @@ -694,6 +693,85 @@ fm_backend_target_exists() { # <backend> <target> [expected-label]
esac
}

fm_backend_target_absent() { # <backend> <target> [expected-label] -> 0 absent, 1 live, 2 unknown
local backend=$1 target=$2 expected_label=${3:-} out status state session pane wsid title
case "$backend" in
tmux)
out=$(tmux display-message -p -t "$target" '#{pane_id}' 2>&1)
status=$?
[ "$status" -eq 0 ] && return 1
case "$out" in
"can't find "*|"no server running on "*) return 0 ;;
*) return 2 ;;
esac
;;
herdr)
fm_backend_source herdr || return 2
session=${target%%:*}
pane=${target#*:}
[ -n "$session" ] && [ -n "$pane" ] && [ "$pane" != "$target" ] || return 2
state=$(fm_backend_herdr_pane_agent_state "$session" "$pane")
case "$state" in
dead) return 0 ;;
live|no-agent) return 1 ;;
*) return 2 ;;
esac
;;
zellij)
fm_backend_source zellij || return 2
fm_backend_zellij_parse_target "$target" || return 2
out=$(zellij list-sessions --short --no-formatting 2>/dev/null) || return 2
if ! printf '%s\n' "$out" | grep -qxF "$FM_BACKEND_ZELLIJ_SESSION"; then
return 0
fi
out=$(fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action list-panes --json 2>/dev/null) || return 2
printf '%s' "$out" | jq -e 'type == "array"' >/dev/null 2>&1 || return 2
if printf '%s' "$out" | jq -e --argjson p "$FM_BACKEND_ZELLIJ_PANE" \
'[.[]? | select(.id == $p and .is_plugin == false)] | length == 0' >/dev/null 2>&1; then
return 0
fi
return 1
;;
cmux)
fm_backend_source cmux || return 2
fm_backend_cmux_parse_target "$target" || return 2
wsid=$FM_BACKEND_CMUX_WORKSPACE
out=$(fm_backend_cmux_cli workspace list --json --id-format uuids 2>/dev/null) || return 2
printf '%s' "$out" | jq -e '(.workspaces | type) == "array"' >/dev/null 2>&1 || return 2
if [ -n "$expected_label" ]; then
title=$(fm_backend_cmux_scoped_title "$expected_label")
if printf '%s' "$out" | jq -e --arg id "$wsid" --arg title "$title" \
'[.workspaces[]? | select(.id == $id or .title == $title)] | length == 0' >/dev/null 2>&1; then
return 0
fi
elif printf '%s' "$out" | jq -e --arg id "$wsid" \
'[.workspaces[]? | select(.id == $id)] | length == 0' >/dev/null 2>&1; then
return 0
fi
return 1
;;
orca)
fm_backend_source orca || return 2
out=$(orca terminal read --terminal "$target" --limit 1 --json 2>&1)
status=$?
printf '%s' "$out" | node -e '
const fs = require("fs");
let data;
try { data = JSON.parse(fs.readFileSync(0, "utf8")); } catch { process.exit(2); }
if (data.ok === false) {
const code = String((data.error && data.error.code) || "").toLowerCase();
process.exit(code === "terminal_not_found" || code === "not_found" ? 0 : 2);
}
process.exit(process.argv[1] === "0" ? 1 : 2);
' "$status"
return $?
;;
*)
return 2
;;
esac
}

# fm_backend_agent_alive: CONFIDENT liveness of a live harness-agent PROCESS
# under <target>, distinct from fm_backend_target_exists's pane-PRESENCE-only
# check above. A secondmate agent that has exited leaves its backend endpoint
Expand Down
Loading
Loading