feat(omp): port full Oh My Pi (omp) support into firstmate - #4
Merged
Conversation
* fix: delegate no-mistakes PR gate to pinned action * no-mistakes(document): Document commit-bound no-mistakes attestations
…uid#3028) * feat(pi): let operators pin a cheaper supervision-branch model Supervision is an easier job than the captain's own conversation, so the Pi supervision branch does not need main's model. A new /supervision-model command opens Pi's own selector over Pi's own catalog of credentialed models, plus a "Follow main" entry, and persists the pick as one <provider>/<model-id> line in this home's gitignored config/supervision-branch-model. Firstmate keeps no model catalog of its own. The branch resolves the pin at every branch build - the first wake of a cold start and the reopen after /new, /resume, /fork, or reload - so the choice survives all of them, and picking also releases the live branch so the next wake reopens the same persistent branch conversation under the new model. An absent, unreadable, or unparseable file means no pin and keeps today's behavior byte for byte: no model option is passed and Pi picks the branch's model exactly as before. A pin naming a model Pi cannot hand back is never silently downgraded onto main's model: the branch refuses to build and the wake falls back to the captain-facing main path naming the unusable pin, which is the extension's existing failure direction. The choice is home-local and not part of secondmate inherited configuration, matching the Pi Calm preference precedent. docs/configuration.md owns the operator-facing schema. Portable regressions cover pin-present on create and reopen, pin-absent default, the command's persistence, cancellation, and live rebind, and both unusable and unparseable pins. The opt-in real-SDK guard proves the vendor surface the pin reads and that an explicit model wins over the model a reopened session recorded. * no-mistakes(review): Fix supervision model runtime and rebind races * no-mistakes(review): Restrict supervision picker to isolated runtime models * no-mistakes(document): Document supervision branch model selection * fix(pi): make the supervision model pin authoritative on every reopen Clearing the pin with "Follow main" removed the file but the next branch build reopened the persistent branch session with no explicit model override, so Pi restored the model that session had recorded - the old pinned model - while the command reported that the branch now follows main. The same gap meant an absent pin did not reliably mean same-model-as-main once a home had pinned once. The pin file's current state now decides the model on every branch build, create and reopen alike, overriding Pi's session-state restore. With a pin, that model. With no pin, main's own current model is applied explicitly, tracked from the contexts Pi already hands the extension plus its model_select event, since the branch is built at wake time with no context of its own. Only when main's model is unknown, or this home's stored credentials cannot run it in the isolated branch runtime, does a build fall back to passing no override at all, which is the behavior from before the pin existed; the branch is never refused over model choice. The command's notification now reports the model actually applied, and says plainly when clearing the pin could not apply main's model instead of claiming a change that did not take effect. No credential handling changes: the branch still relies entirely on the stored credentials its own runtime already holds, and the picker stays restricted to models that runtime can resolve. Colocated regressions cover pin present on create and reopen, clearing the pin returning a reopened branch to main's model and specifically not the old pinned one, an unparseable pin behaving as no pin, and the unknown-main-model fallback to no override. * no-mistakes(review): Make unpinned supervision follow main model changes * no-mistakes(document): Correct supervision model documentation
…nguid#3079) * feat(pi): let /supervision-model pick the branch's reasoning effort Supervision is an easier job than the captain's own conversation, so the Pi supervision branch does not need main's reasoning effort any more than it needs main's model. /supervision-model now settles both in one flow: the existing model picker, then a follow-up effort picker built from Pi's own supported thinking levels for the model just chosen. Firstmate keeps no effort catalog of its own; the menu, the clamp, and the vocabulary all come from Pi. The pick persists as one line in this home's gitignored config/supervision-branch-effort, independent of the model pin: a captain may pin a model, an effort, both, or neither. The effort pin's current state decides the branch effort on every branch build - the first wake of a cold start and the reopen after /new, /resume, /fork, or reload - and overrides Pi's restore of whatever level a reopened branch session recorded, which is what keeps "Follow main" honest. With no pin, main's own current effort is applied explicitly and followed live through Pi's thinking_level_select event, the same way an unpinned branch already follows main's model, and the two selections now share one build revision so either change invalidates an in-flight build. The branch is never refused over effort. Pi owns the clamp, so a pinned level the branch's model cannot run becomes that model's nearest supported level while the captain's raw pick is kept for a model that supports it, and the command reports the level the branch will really run at rather than the raw pin. A token Pi would not recognize at all is treated as no pin rather than passed to that clamp, which would otherwise collapse a typo into the model's lowest level. Only when main's effort cannot be read either does a build pass no effort override at all, which is the behavior from before this file existed. Pi's own effort vocabulary is pinned by a bidirectional type assertion against Pi's getThinkingLevel return type, so the tracked strict typecheck against the installed package fails the moment Pi adds or removes a level. docs/configuration.md owns the operator-facing schema for both pins. Portable regressions cover the pin on create and reopen, model-only and effort-only pins working independently, clearing a pin returning the branch to main's effort, live-follow of a mid-session change, the clamp, an unrecognized token, the unknown-main-effort fallback, and the command's two-step flow, persistence, cancellation, and honest reporting. The opt-in real-SDK guard proves the vendor surface all of that rests on, and also repairs a pre-existing gap that left it unable to load the extension at all. * no-mistakes(review): Resolve effective branch effort honestly * no-mistakes(document): Clarify Pi-owned effort picker behavior
…3093) * fix(supervision): silence empty board closes and decouple the heartbeat Two unrelated sources of noise put routine supervision events in the captain's chat. An empty Lavish board close - the captain reads a review surface, says nothing, and closes it - became a check wake whose entire content was that nothing happened. Suppress it at its source instead of routing it anywhere: the generic runner gains a `silent` adapter seam mirroring the existing `terminal` one, and the Lavish adapter answers it for exactly one positively-determined shape, an `ended` session carrying no queued content block. A silenced result is recorded durably handled so it does not return on a later reconcile. Everything else announces unchanged - a `Send & End` close carrying the captain's real answer, an `ended` result still carrying content, a waiting or missing session, an unreadable result, and every adapter that implements no `silent` command at all. The keyed-answer feed is untouched, so suppressing an announcement never suppresses the captain's own answer. A fleet heartbeat was deferred to main merely because some unrelated check row happened to be sitting unread, which put a routine fleet review in the chat for a reason that had nothing to do with the fleet. A check row is permanently main-owned, so it is now excluded from a heartbeat claim rather than vetoing the scan, exactly as in every other mode. What all-or-nothing guarantees is unchanged: the branch takes every branch-ownable unread row or none of them, and an unresolvable task-local row, an unknown row kind, or an unreadable queue still defers the whole review to main. Main is still woken for the check on that check's own triggering close, so nothing starves. Main-only classes are unchanged and now each covered by a test: Relay mentions, credential failures, merge confirmations, real board answers, and watcher-failure repair. The per-actor acknowledgement and no-cross-swallow properties are untouched. * no-mistakes(review): Fail closed on all Lavish content headers * no-mistakes(review): Suppress false unacknowledged status for silenced results
kunchenguid#1967) * fix(classify): read the decision fold through a correlation token status_line_verb stripped a trailing [key=...] from a status line's prefix but left everything else glued to the verb, so a line carrying the correlation token bin/fm-pending-reply-lib.sh embeds and a secondmate echoes back matched no arm of _fm_decision_fold_line. Such a line folded as ordinary status in both directions: a needs-decision or blocked opener never opened its key, and a resolved or captain-held closer never closed one. The same glued verb also hid correlated done and blocked lines from status_is_captain_relevant and status_is_terminal_verb, and let correlated working and resolved lines leak through the free-text fallback the nonterminal guard was meant to stop. The verb parse now walks whole words and drops only a token of the exact shape a firstmate library writes - corr=<16 hex>, plus the bracketed form bin/fm-secondmate-report.sh emits - before or after the key token, unkeyed, or doubled. An arbitrary name=value word is deliberately NOT skipped: skipping unknown tokens would let free text carrying an equals sign reduce to a bare verb and impersonate a transition, which is the takeover the strict parse and _fm_decision_key_transition_allowed exist to prevent. A prefix with no corr= substring is returned byte-for-byte as before, so every line without a token keeps its exact historical verb. FM_OPEN_DECISIONS_FOLD_VERSION goes to 3, because every cursor persisted under the previous reading carries an open set computed while correlated lines were invisible and must be rebuilt from byte 0. Measured over a real 383-line status log: 254 lines keep byte-identical captain-relevance, pause, terminal-verb and captain-held verdicts, and all 129 changed lines carry a valid token - 14 correlated done/blocked/ needs-decision lines become captain-visible, and 20 correlated working/resolved lines stop being escalated on prose alone. * fix(review): Captain, block token-first decision impersonation * fix(document): Clarify normalized status verb ownership * fix(classify): reconcile the correlation-token read with the tag-stop parser Rebasing onto main put this change beside kunchenguid#2280, which made verb parsing stop at the first "[name=value]" tag. Both edit status_line_verb with different intents, so the resolution keeps both rules rather than letting one overwrite the other: - kunchenguid#2280's tag stop is kept verbatim and now owns every BRACKETED tag, including the "[corr=...]" form fm-secondmate-report.sh writes. The bracket-unwrapping arm this branch had added to the token test is therefore removed as unreachable. - This branch's token walk is kept and narrowed to the UNBRACKETED token fm-pending-reply-lib.sh writes, which the tag stop does not reach. Two consequences of standing beside kunchenguid#2280 rather than before it: The fold version had collided at 4: kunchenguid#2280 spent it on the tag-stop parser and this branch had spent it on the token read. A cursor persisted under kunchenguid#2280's reading predates this one and must still be rebuilt, so the version moves to 5. A bracketed impostor is dropped from the malformed-token list. On main today "resolved [corr=deadbeef] [key=victim]:" already reads as the bare verb, as does "resolved [anything at all] [key=victim]:", because the tag stop ends the parse at the opening bracket regardless of content. That is kunchenguid#2280's reviewed contract; asserting otherwise here would narrow it. The unbracketed impostors it owns stay strict and still fold as prose. Adds a consumer test for the two verb-string case arms that postdate this branch: fm-supervise-daemon.sh's transient-stale arm and fm-crew-state.sh's map_log_state. * fix(review): Captain: Seed cursor migration fixture with version four * fix(document): Clarify voice status normalization ownership
…unchenguid#3115) * fix(bin): Cursor-Park unter Pi-Host stilllegen. pi-cursor-sdk lädt .cursor/hooks.json in die Pi-Sitzung und parkte einen zweiten Watcher; das erzeugte rearm-resurface und brach laufende Rückfragen ab. Bei PI_CODING_AGENT=true beendet der Park sofort, native cursor-agent Primaries bleiben unverändert. * fix(bin): Cursor-Park trotz PI-Leak nur ohne Cursor-Identität stilllegen. Stand-down gilt nur bei PI_CODING_AGENT=true ohne CURSOR_AGENT und ohne CURSOR_INVOKED_AS. Handgestartete cursor-agent Primaries mit geerbtem PI-Marker parken weiter. * no-mistakes(document): Document Cursor park Pi-host stand-down * fix(bin): no-mistakes-Mindestversion auf 1.46.0 anheben. Die PR-Attestierung verlangt ab 1.46.0 strukturierte Pipeline-Schritte; der Bootstrap-Floor blieb bei 1.31.2 und ließ zu alte Builds zu. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
…chenguid#3099) * fix(pi): make /supervision-model's model list bounded and searchable Pi's generic extension selector renders every option at once with no search box, so a real eligible catalog ran off the top of the terminal. The model step now draws the same rows through Pi's own SelectList - the bounded scrolling primitive behind Pi's /model picker - with Pi's own Input and fuzzy filter above it for search, keeping 'Follow main' first, the branch-runtime eligibility filter intact, and the pick branch-only. Pi's ModelSelectorComponent is deliberately not reused: its selection handler writes the captain's default model through Pi's settings manager, which would move main's conversation as a side effect of pinning the branch. The effort step's menu is a handful of levels and stays on Pi's plain selector dialog. * no-mistakes(document): Clarify supervision picker documentation * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes(document): Document searchable supervision model picker * no-mistakes: apply CI fixes
* fix: make a landed merge leave a durable outcome A merge was the one lifecycle event that left no record outside the merging agent's memory. bin/fm-pr-merge.sh ended at the forge call, and a home merging under standing authority never waits for the merge poll that would otherwise confirm it, so three real merges reached the captain as silence. bin/fm-merge-outcome-lib.sh is the single owner of that record. A secondmate home reports the landed PR upward on the same parent reply channel its terminal-outcome backstop already uses; a main home records it on the durable wake queue. The record is at most once per task and canonical PR identity, and only a merge that actually landed produces one. The merge poll feeds that same channel when it detects a merge this home did not perform, so the captain's own forge merge and a merge firstmate performed itself produce one consistent outcome instead of two reporting paths. No new state file and no second watch path. Two smaller gaps from the same failure: - A mate charter listed its report triggers without naming a landed merge. Under standing merge authority nothing is ever "ready for review", so the enumerated list silently omitted the case that matters. - A secondmate home seeded without its parent binding failed every terminal-outcome report for the same reason, and the diagnostic never named the binding. It does now. * no-mistakes(review): Harden durable merge outcome reporting * no-mistakes(review): Make merge race regression deterministic * no-mistakes(review): Make merge outcomes retry-idempotent and forge-confirmed * no-mistakes(review): Unify merge publication under canonical outcome marker * no-mistakes(review): Publish merge outcomes before committing dedup markers * no-mistakes(review): Document at-least-once merge outcome recovery * no-mistakes(review): Use supported GitHub confirmation and update recovery docs * no-mistakes(review): Preserve distinct merge wakes by PR identity * no-mistakes(document): Document durable merge outcome semantics * no-mistakes(test): Make merge outcome interleaving test deterministic * no-mistakes(document): Clarify merge outcome documentation ownership * fix(lint): keep the merge-outcome library an analysis boundary bin/fm-watch.sh followed the new merge-outcome library's source graph, which reaches the wake queue, PR identity, and secondmate parent libraries. Expanding that inside an already-large lint root pushed ShellCheck's external-source analysis past the bounded CI lint worker: the Lint job was killed with SIGTERM after five silent minutes, twice, having emitted no diagnostics at all. Make it an analysis boundary, exactly as the transition and inbox owners directly above and below it already are and for the same stated reason. Coverage is unchanged because the library is a canonical lint root in its own right and is still linted as one. Measured locally: the watcher goes from not terminating within 120s to 9s clean, and the library alone lints in 1s clean.
…chenguid#3129) * fix(bearings): keep an inventory-mismatch home readable, and mark warnings as repairs A backlog-vs-metadata inventory mismatch inside a secondmate home was being reported as "we cannot read that home", which discarded that home's open captain calls, queued work, landed work, and live workers from the whole Bearings digest. The main home already treats the identical mismatch as a harmless disclosure; this makes the secondmate path agree. - fm-fleet-snapshot.sh: the invalidity gate now passes orphan_in_flight, unowned_current, and terminal_in_flight through the partial-structured carve-out alongside child_current_unavailable, so those homes keep their decisions, holds, queued, landed, and live work and leave unreadable[]. missing_backlog and unstructured_current stay on the discard path, because there the backlog itself is untrustworthy. - fm-fleet-snapshot.sh: the same three kinds no longer collapse the home's own classification to "unknown"; the real captain_decision / active_child_work / externally_held classification survives and invalidity carries the warning. An unavailable child state still collapses it, including when a mismatch masks it under strict-invalidity precedence. - secondmate_landed.partial now keys on partial-structured trust rather than an unknown state, so an inventory-mismatch home is still disclosed as partial. Ask the home that owns the wrong books to fix them: - bin/fm-secondmate-reconcile.sh sends exactly one reconcile instruction per mismatch episode through the ordinary steering transport. A persistent mismatch keeps its episode identity and never re-nags; a changed mismatch earns one more ask; a repaired one is forgotten so a recurrence is asked about again. The parent never touches the mate's own files, and a failed send records nothing so the next run retries it. Give integrity warnings their own look on the board: - charted rows take an optional kind of "queued" (the default) or "warning". A warning badges "needs repair" instead of "waiting" and is excluded from the Charted Next count, so alarms stop reading as dispatchable queued work. No fifth board section, and every existing payload stays valid. Tests pin the new policy behaviorally: the retained surfaces and classification for all three mismatch kinds, the still-discarding unstructured_current and missing_backlog cases, the once-per-episode reconcile ask through real durable steering records, and the board rendering exercised through the shipped template under a minimal DOM shim. * no-mistakes(review): Make reconcile dedupe atomic and warnings non-dispatchable * no-mistakes(review): Preserve reconcile identity and reject stale snapshots * no-mistakes(review): Order snapshots uniquely and canonicalize episode identities * no-mistakes(review): Add fire-and-forget reconcile and separate warning overflow * no-mistakes(review): Exclude fire-and-forget from escalation and track reconcile background * no-mistakes(review): Run reconcile enqueue inline across all adapters * no-mistakes(review): Track reconcile clears across strict-invalidity homes * no-mistakes(review): Persist reconcile transitions atomically * no-mistakes(document): Document reconcile and fire-and-forget contracts * refactor(bearings): replace the reconcile episode dedupe with a 4-hour cooldown The reconcile ask needed to fire once per problem without nagging on every recap. The episode-precise record that tried to do that had to be correct in every direction at once - order two concurrent snapshots, tell a repair from a new problem, and never lose a clear - and each direction it got wrong either swallowed a nudge or sent a duplicate. A per-home cooldown removes the whole class. One durable timestamp per home, one nudge per four hours, and nothing to get stale, mis-order, or mis-classify: a home in mismatch is asked once, later recaps stay silent, and a mismatch still sitting there after the window earns one gentle re-nudge. - bin/fm-secondmate-reconcile.sh: state/<id>.reconcile-nudged holds the epoch second of the last ask; FM_RECONCILE_COOLDOWN_SECONDS names the window. The episode identity, ordering generation, pending/clear transitions, and delivery-identity reuse are all gone. A known-undelivered send starts no cooldown so the next run retries it; an unconfirmed one does, because a duplicate ask is worse than one the mate may already hold. - bin/fm-fleet-snapshot.sh, bin/fm-bearings-snapshot.sh: drop the snapshot `observation` monotonic identity, which existed only to order those records. - bin/fm-teardown.sh: retire the cooldown record with the endpoint's other runtime artifacts, so reseeding a retired id is not silenced by its predecessor's window. The inline durable fire-and-forget send is unchanged, and the projection fix and the warning surface are untouched. Tests follow the behavior: the cooldown suite now pins one ask per window, the re-nudge after it, the four-hour boundary, per-home independence, and that the ask stays out of a re-ring ladder that still rings an ordinary steer beside it. The obsolete observation-ordering test is deleted with the machinery it covered. * no-mistakes(review): Serialize reconcile cooldown commits with mate lifecycle * no-mistakes(review): Reject stale reconcile snapshots across mate reincarnations * no-mistakes(review): Start reconcile cooldown after delivery completes * no-mistakes(review): Keep reconcile sends nonblocking and remove pending residue * no-mistakes(document): Document reconcile skip and stale-endpoint behavior * no-mistakes(lint): Fix reconcile test subshell lint warning * no-mistakes: apply CI fixes
…#3140) * fix(bin): stop dropping reconcile nudges for markerless remote secondmates A persistent remote secondmate's parent-side state/<id>.meta never carries spawn_gen: bin/fm-spawn.sh's spawn_remote_secondmate() is its sole writer and never writes one, because that incarnation identity does not apply to a remote route. fm-secondmate-reconcile.sh's row filter required a non-empty spawn_gen matching an identifier regex, so every such row was silently dropped before the per-row loop ever saw it: no sent/stale/failed line, no cooldown record, nothing sent, and no trace of why. Give a legitimately markerless persistent remote secondmate a safe substitute identity - its recorded remote_host - instead of weakening the spawn_gen check for rows that do have a generation: - bin/fm-secondmate-reconcile.sh: carry host through the row projection for both fm-fleet-snapshot.v1 and fm-bearings.v1 documents, and admit an empty spawn_gen instead of filtering the row out. A new revalidate_identity() compares the sampled spawn_gen against current metadata when one was sampled (unchanged), or the sampled host against the metadata's remote_host when none was sampled and the metadata still carries no spawn_gen of its own. A row with neither a spawn_gen nor a host has no safe identity at all and fails loudly instead of vanishing, exactly the visibility the original bug lacked. - Rows now join on the ASCII unit separator rather than @TSV: bash's IFS-whitespace read collapses consecutive tabs, which would have silently dropped a legitimately empty field again. - bin/fm-bearings-snapshot.sh: thread host through the secondmate_reconcile projection so the fm-bearings.v1 path (the one bearings itself feeds to the reconcile hook) carries the same substitute identity. - tests/fm-secondmate-reconcile.test.sh: end-to-end coverage through the real remote transport (fm-on.sh + fm-remote-secondmate-control.sh against a genuinely seeded remote home) for a markerless mate nudged once per cooldown window, a stale/replaced remote route refused exactly like the existing local spawn_gen case, and a row with no identity at all failing loudly rather than being swallowed. * no-mistakes(review): Enforce markerless remote host identity during final delivery * no-mistakes(document): Document markerless remote reconciliation safety
…decorated (kunchenguid#3147) * fix(watch): hand a busy declared pause to the away-mode daemon undecorated While away mode is active the daemon owns triage and the watcher reverts to one-shot, handing over plain wake identities the daemon classifies itself. The busy-turn bound was the one stale path that did not: with afk active it ran the wedge timer, so the daemon received a wake already decorated as a possible wedge. That decoration outranks the daemon's own verdict. handle_wake escalates an enriched wedge reason before its pause classification can apply, so a crew that declared the wait itself - a `paused:` external wait or a verified captain-held transfer holding a live foreground call - was wedge-escalated once per FM_STALE_ESCALATE_SECS for as long as the wait lasted, the escalation count climbing into demand-deep-inspection on a pane nobody needed to inspect. Measured on the pre-fix tree, five consecutive re-arms produced five escalations. busy_turn_bound_check now reads the declaration before the afk branch: away mode hands off the plain window identity, one-shot per distinct stale hash, leaving normal-mode pause bookkeeping unwritten because the daemon owns it there. The daemon then classifies the wait itself and self-handles it on the long cadence. Normal-mode behavior is unchanged, and lifting the declaration still restores the busy-pane wedge escalation on the same pane. The regression covers all three: the undecorated handoff with no wedge timer or escalation counter, the one-shot on re-arm that the escalation ladder used to climb, and the restored wedge escalation once the declaration is lifted. * no-mistakes(review): key afk busy-pause handoff on declaration, clear wedge state * no-mistakes(document): docs: scope away-mode busy-bound handoff to declared waits * no-mistakes(document): docs: note afk busy-bound handoff in watcher header --------- Co-authored-by: Talon Stark <talonstark@gmail.com>
…unchenguid#3121) * fix(bin): explain a pooled slot's stale submodule refusal A pool slot whose submodule pin moved is refused with "is not clean; refusing to discard uncommitted work", while the operator's own `git status` in that slot reads clean. The message names no submodule, no pin, and no remedy, so the refusal is unreadable and the slot looks wedged for no reason. That is the failure that jammed three slots in a row when a submodule pin moved. The refusal itself was never the bug and is unchanged: the gate still refuses, and still touches nothing. It now distinguishes the one case it can prove and says what it found - the submodule, the pin the slot has, the pin the base records, and the command that clears it. The diagnosis is deliberately conservative, because ` M <path>` alone cannot tell a stale pin from real work. An entry is reported as stale only when every reported entry is a gitlink whose submodule is internally clean and whose recorded pin actually differs. A submodule holding uncommitted work, untracked files, or an unpushed commit therefore keeps the original uncommitted-work refusal, even when its pin is also stale - the remedy command would be wrong there, and the conservative refusal is the safe answer. Nothing is converged, synced, initialized, or deleted. There is no new failure path: a slot that launched before still launches, a slot that refused before still refuses, and projects that configure a submodule `ignore` are read exactly as before. Paths are read with core.quotePath=false so a non-ASCII submodule is named rather than falling back to the unreadable message. Tests keep the reproductions that prove the message is accurate: the stale-pin diagnosis (which fails against the previous refusal), work inside a submodule still refused as uncommitted work, and a stale pin carrying real work refused conservatively rather than called stale. Each asserts the slot is left untouched. * no-mistakes(review): require remote containment before calling a submodule pin stale * fix(bin): stop printing a remedy the containment check cannot stand behind The stale-pin diagnosis printed `git submodule update --checkout` as the command that clears the slot. The containment check behind it reads local refs only and never fetches, because this gate has to stay usable offline. A remote-tracking ref that has gone stale - its upstream branch deleted or force-pushed, and never pruned - still reads as containment, so a commit that is really unpushed can look contained and that command would move the submodule off it. Naming the submodule and both pins is the whole point of the diagnosis: it turns "is not clean", on a slot whose own `git status` reads clean, into a statement of which submodule drifted and where it drifted from. The operator can choose the remedy from that, seeing the whole picture. Printing an instruction that rests on a judgement which can be fooled is worse than printing none, so it is dropped. The limitation is now stated where it applies, in the script header and beside the check itself, rather than left for a reader to discover. No fetch is added: the gate stays offline-safe by design. Nothing else changes - the same conditions are refused, the slot is still never touched, and a submodule carrying real work or an unpushed commit still keeps the conservative uncommitted-work refusal. * no-mistakes(review): bound submodule containment probe to first commit
* fix(pi): type captain supervision outcomes so main relays them A captain-relevant branch outcome reached main as a bare user message with no marker of origin or required action, written in main's own captain-facing voice, landing in a tail that often already held several such notes. Pi keeps only a custom message's content when it builds the provider request, so customType and display never reach the model and content was the only place that identity could live. Main could not tell an incoming outcome from its own earlier answer and sometimes re-emitted that answer instead of relaying the outcome, losing it. Measured against real Pi 0.84.1 on openai-codex/gpt-5.6-sol: 6 failures in 24 turns, rising to 3 in 6 once one stale answer was already in the tail, which is how one captain conversation saw six identical messages in a row. The same scenario with the outcome typed failed 0 times in 14 turns. Wrap only the captain-verdict note in the branch-outcome operational kind owned by bin/fm-operational-input.sh. Delivery is otherwise unchanged: still display: false, still one triggerTurn follow-up, so the turn remains the single captain-visible outcome and no hidden note is ever shown twice. Routine notes stay plain because their renderer reads the glyph off the front of that same string. An outcome that cannot be encoded degrades to the same instruction as plain text rather than being lost, matching this file's stated failure direction. The existing assertions could not catch this: they pin the sendMessage options and never look at what main receives. Add a portable regression that classifies the delivered payload with the real protocol executable, and a live guard that runs the real Pi SDK's own convertToLlm to prove content is the entire model-visible payload. * no-mistakes(document): Document typed Pi captain outcomes
… or enriched wedge (kunchenguid#3155) * fix(bin): keep a busy pane from retiring a still-declared wait's window The away-mode daemon's pause re-surface recheck (housekeeping step 2b) read a busy pane as "the crew resumed" and dropped the declared-wait marker, without re-reading that the crew's own latest status line still declared the wait. That inference is not safe, because a declared wait can legitimately hold a pane busy: a worker sitting on a long foreground call keeps that call live for as long as the wait lasts. The marker is then cleared while the declaration still stands, and migrate_watcher_pause_markers recreates it with a fresh timestamp on the very next tick, so the window restarts forever and the wait never matures into its one bounded recheck. Away mode makes that terminal. Since the watcher half landed, a busy pane under a declared wait is handed to the daemon exactly once per declaration and never woken again while the declaration stands (bin/fm-watch.sh, busy_turn_bound_check), so this recheck is the only thing left that can re-surface the pane at all. Measured end to end on a throwaway state root, away mode active, a pi pane busy past FM_BUSY_TURN_MAX_SECS, status still `paused:`, over six PAUSE_RESURFACE_SECS windows: 0 captain-facing rechecks before this change, 6 after - one per window, with the marker reset each time. The fix drops only the busy arm of the 2b probe, leaving it an endpoint-readability check: exit code 2 still means the capture failed, so the endpoint is gone and the marker goes. The loop head above already drops the marker the moment the status line stops declaring the wait, so nothing else is needed to end the routing, and the reconcile path runs before the probe ever reads a pane. tests/fm-daemon.test.sh: test_housekeeping_paused_resumed_cleared pinned the old inference on purpose - its fixture's status line still read `paused:` while the pane was busy, and its comment read "A pause whose pane became busy again (the crew resumed)". Its fixture now resumes the way a crew actually resumes, by appending a non-declaring status line, and it asserts its own busy verdict first so it cannot silently decay into the idle-pane case that test_housekeeping_paused_unpaused_cleared already covers. What it pins is now the inverse guard: a busy pane must not GATE the clear either, so an over-correction that kept the marker alive whenever the pane is busy would fail it. test_housekeeping_busy_declared_wait_matures_its_window is the new regression, over both declaration forms. It asserts the busy verdict, then that ticks inside the window neither escalate nor let the marker be recreated with a fresh timestamp, then exactly one recheck past the window named for the right human and never a wedge, then silence on the next tick inside the reset window. It fails on unmodified main with "produced 0 escalations past its window, expected exactly one". Refs kunchenguid#3149 * fix(bin): let a declared wait outrank an enriched wedge escalation handle_wake classifies a stale wake through classify_stale, which returns a `pause` verdict for a crew whose latest status line declares an external wait or a verified captain-held transfer. It then threw that verdict away whenever the wake reason matched `idle *s, possible wedge, escalation *`, so the watcher's enriched wedge decoration outranked the crew's own declaration and a healthy declared wait was escalated once per FM_STALE_ESCALATE_SECS for as long as the wait lasted. The enriched reason earns its precedence over the daemon's cheaper status-log absorption honestly - it carries the watcher's escalation count and its explicit "do not re-absorb on the run-step/pane state alone" demand. A `pause` verdict is not run-step or pane state. It is the crew's own declaration that this pane waits by design, which is precisely the question the wedge timer cannot answer for itself, so it is the one verdict that decoration must not override. The two classifications genuinely disagree in steady state rather than only in a race: a crew that declares `paused:` while its no-mistakes run is still attributed to its code reads `working` to the watcher's pause_state_class, so the watcher takes the wedge timer while the daemon's classify_stale reads the status log and correctly returns `pause`. The wait stays bounded, not silenced. Absorbing to the pause action records the declared-wait marker and drops wedge aging, and housekeeping (2b) then owns the re-surface, so the pane still reaches the captain - once per PAUSE_RESURFACE_SECS as an explicit "recheck whether the wait still holds", instead of once per FM_STALE_ESCALATE_SECS as a possible wedge. Measured on a throwaway state root over five wedge cadences for one declared wait: 5 escalations climbing to demand-deep-inspection before this change, 0 after, with the one bounded recheck still delivered. tests/fm-daemon.test.sh: test_stale_diagnostic_wedge_survives_busy_housekeeping's `paused` case pinned the old precedence on purpose, asserting exactly one escalation carrying the demand-deep-inspection payload. That case now asserts the pause cadence instead - no escalation inside the window, pause tracking recorded - while the `working` and `prior-terminal` cases keep asserting the enriched wedge verbatim, so the override itself is still pinned everywhere it is correct. test_enriched_wedge_under_declared_wait_uses_pause_cadence is the new regression. It asserts the fixture's own classifier verdict is a pause first, so the case cannot go vacuous, then drives four consecutive wedge-cadence deliveries in both the plain and demand-deep-inspection forms through the real handle_wake and housekeeping pair, then matures the window for exactly one awaiting-external recheck, then lifts the declaration and requires the same enriched wedge to escalate again unchanged. It fails on unmodified main at the first delivery. Refs kunchenguid#3149 * no-mistakes(review): align afk skill recheck wording with still-declared contract * no-mistakes(document): daemon doc comments: pause window ages on declaration --------- Co-authored-by: Talon Stark <talonstark@gmail.com>
* fix(bin): make Claude auto-arm continuity self-heal past a hung claim On a Claude primary, a Stop-hook auto-arm process that hung mid-arm held the single-flight owner lock with its epoch ledger frozen at outcome=arming, and the abandonment proof read any live lock holder in arming as legitimately deciding forever. Every later Stop firing exited 0 at the lock, the turn-end guard kept deferring to the hung owner as recovery under way, and the watcher was never auto-re-armed again for the rest of the session - supervision survived only on manual arms and lapsed between them (the 2026-08-26 watcher flap). Corrections layered onto the lock-held-across-arm shape each reopened the same concurrency class one level down, so this replaces the claim machinery wholesale with a generation-based optimistic design: - The epoch ledger's monotonic sequence IS the claim generation; the two-line entry (classic epoch record plus the claimant's MANDATORY pid-identity) is the claim. Every firing defers to a live OPEN claim: outcome arming, owner alive, identity recomputes and matches, and not stuck (entry and watcher beacon both older than the guard grace). - A finished, dead, identity-mismatched, identityless, or stuck claim is superseded by simply taking the next generation - no signalling or revocation of a steady-state predecessor. - No mutex is held across arming or output; the owner lock survives only as a micro-mutex around individual ledger writes. A superseded owner goes completely silent: ownership is re-verified before every arm invocation, episode-state mutation, ledger write, and continuation. - The irrevocable commit point of a translation is the exit status (the harness delivers the collected stderr only on exit 2), so the owned terminal ledger write is the atomic commit: the winning generation exits 2 unconditionally after it, a refused one exits 0 silently even after printing, and the once-per-episode failure notice commits in the same owned critical section as the winning failed write. Two bounded residuals are documented accepted intent: an owner dying between its owned write and its own exit, and a hung old-build owner resuming during the one legacy upgrade window. - The pre-generation lock-holding claim shape keeps defer-or-reclaim behavior through a legacy shim: a live identity-verified stuck owner is retired via TERM (with a queued TERM sufficient when the owner is stopped) before its lock is removed, an unverified or identityless pid is never signalled but never blocks a proven-abandoned reclaim, and the lock's identity evidence is grafted into the ledger (mtime-preserving) so pid-reuse protection survives the lock. - The guard reads the same predicates for recovery ownership and its terminal fail-open (which re-checks for a live open claim under the held locks before committing the attended alarm), with ledger reads anchored to line 1 so the identity line can never confuse them. Behavioral regression coverage exercises all three edge classes through the real hook and guard - a live open claim defers with no lock held, a stuck claim is superseded and the home re-arms, and an end-to-end run with a genuinely hung owner shows a concurrent firing deferring promptly mid-arm, a later firing superseding the stuck owner, and the superseded owner exiting silently without a second translation - plus the identityless/reused-pid loopholes, the superseded-owner arm boundary, and the legacy TERM, SIGSTOP, and signal-free reclaim paths. * no-mistakes(review): Refuse auto-arm commits when notice marker creation fails * no-mistakes(review): Make episode reset atomic with generation ownership * no-mistakes(document): Update auto-arm generation and commit documentation
# Conflicts: # AGENTS.md # docs/configuration.md
omp (Oh My Pi, @oh-my-pi/pi-coding-agent) sets both OMPCODE=1 and CLAUDECODE=1 on every bash-tool child process, so checking CLAUDECODE first silently misidentified omp sessions as claude. Test OMPCODE first, same precedence-fix pattern already used for the Cursor marker collision in this file, and add ancestry-based detection as a second layer. Also record the empirically verified (but partial) Oh My Pi adapter facts in harness-adapters so dispatch continues to fail closed until a launch template and busy-state contract land in a follow-up. (cherry picked from commit 12e8fad585c58e8d3b063f05e5779c76a203c93e)
…-dispatch.json Verified live 2026-08-24 on omp v18.0.4: interrupt (single Escape), exit (/exit), and a full real crew dispatch smoke test (spawn, run, report, exit, teardown) on the openai-codex/gpt-5.6-luna model. - bin/fm-spawn.sh: omp launch template (--approval-mode yolo, --model, --thinking), model/effort flag wiring, and an explicit --secondmate refusal on omp (no primary supervision protocol yet, same as muse). - bin/fm-control-lib.sh: omp control-plane mechanics (interrupt key, exit command, kind restriction) mirroring the verified facts. - .agents/skills/harness-adapters/SKILL.md: omp moved from partially-verified to VERIFIED CREWMATE/SCOUT, with the live evidence and the model-role-mapping rationale. - AGENTS.md: omp added to the verified-harness list, crewmate/scout only. config/crew-harness and config/crew-dispatch.json (local, gitignored) now route crew dispatch to omp, with each dispatch rule's model selected from omp's own configured modelRoles (~/.omp/agent/config.yml) per captain instruction, superseding the 2026-08-21 claude-only restriction for omp dispatch. (cherry picked from commit 16cc445d95fa677ee6a6dd5b647fe51eedd6e60e)
omp had no working busy-state hook and no composer mapping, so Herdr's agent panel and fm-crew-state.sh could never show a real state for an omp crewmate/scout once idle. Both gaps are closed: - bin/fm-spawn.sh: omp's launch template now loads a per-task state/<id>.omp-ext.ts extension via an explicit -e path, mirroring the pi-ext pattern. omp's extension API is Pi-compatible but its settle event is agent_end (not agent_settled) and carries its own willContinue flag; the extension goes idle only when willContinue is falsy AND ctx.isIdle() also confirms it, the same double-check Pi's extension uses. Verified live end-to-end against the real omp v18.0.4 binary: busy mid-turn, idle only at genuine settle, turn_end staying a notification-only touch. - bin/fm-busy-lib.sh: fm_busy_sources_for_harness now trusts the omp-ext source for harness=omp. - bin/fm-composer-lib.sh: taught the shared screen classifier omp's real composer shape (composer.shape=box, live-verified): a top row that permanently embeds a status/title line and a bottom row that is simultaneously the closing rule and the last content row. This needed a new "capped" shape alongside the existing bordered/bare/left-bar/ separated catalogue, since the generic bordered shape's content_rows>0 gate always misses the common zero-interior-row case. Also registered omp's busy-indicator delivery regex (a stable trailing "esc" cancel-hint token; the spinner's working-message text is dynamic and never stable enough to match). - bin/fm-teardown.sh: clean up the per-task omp-ext.ts file alongside the other per-adapter artifacts. - tests/fm-busy-adapter-wiring.test.sh: drive the real fm-spawn- generated omp extension through a plain Node host and the real fm-busy-event.sh writer (agent_start, willContinue, ctx.isIdle(), turn_end, ordering, stale-incarnation rejection). - tests/fm-composer-lib.test.sh: matrix coverage for the new capped shape (idle, typed, wrapped, extraction fidelity). - harness-adapters skill: omp's Busy state row and the "remaining work" note updated to match the verified reality, with the exact verification boundary disclosed (a full nested fm-spawn dispatch from inside this task's own isolated worktree was not run, to avoid mutating the shared firstmate home; the live binary round-trip and the automated real-extension-file tests are what was actually done). Full existing composer-lib, composer-ghost, busy-state, busy-adapter-wiring, backend-herdr, tmux-submit-busy, and spawn-dispatch-profile suites re-run with zero regressions. (cherry picked from commit bed42b8fe5b00260bfcde2b30141e92804670ffe)
(cherry picked from commit 7e01c77fd97cf94ee295deb3a5745801c3c3546f)
owner.restoring stayed true through the await sendWake(...) call after a restore attempt concluded (success or exhausted retries), so if the confirmed successor watcher exited during that window its own close handler saw restoring still true and silently dropped the failure, leaving watcher supervision down with no scheduled retry. Clear the flag as soon as the retry loop's outcome is decided, before the async notification, so a close event racing with wake delivery is handled normally. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e tasks-axi-lib's new archive-read role
omp (Oh My Pi) runs on the same Claude Agent SDK and sets CLAUDECODE=1, but its process is named "omp" with no separate "claude"-named ancestor, so fm-session-lock-lib.sh's ancestry walk could never locate it and every omp-run firstmate primary stayed permanently read-only. Gate the recognition strictly on the CLAUDECODE=1 marker, never a bare name match, scoped to session-lock identity only.
… pids Greptile flagged that fm_harness_process_matches's omp branch read $CLAUDECODE from the checking process's own environment even when classifying an unrelated pid pulled from a lock file - a live non-Claude omp holder checked from a Claude session would be misclassified as alive, and a genuinely live Claude-backed omp session checked from an unmarked context would look stale and lose its lock. The marker is only sound evidence for a pid that is a verified member of the checker's own process ancestry, since env vars are inherited top-down. fm_harness_ancestry_pids now passes self=1 only for pids it discovers by walking $$'s own kernel-reported ppid chain, and fm_harness_pid_alive computes self by checking membership in that same walk before trusting the marker for a lock-file pid, rather than assuming self from the caller's context.
…require The harness-dependent CLAUDECODE-marker check landed with only a portable regression against a stubbed ps. Add the required live-harness-optin counterpart, driving a real installed omp process and its own bash tool to confirm the two vendor facts the check depends on: that omp's own comm reports base name "omp", and that a real omp session's own process environment carries CLAUDECODE=1. Record the dated result and update the harness-adapters note to reflect the self/foreign-pid distinction already shipped in bin/fm-session-lock-lib.sh and bin/fm-lock.sh.
…olation - tests/fm-omp-harness.test.sh (detection matrix, from pr-2935/pr-2503) - tests/fm-omp-primary-types.test.sh (strict no-emit typecheck of the .omp/extensions primary stack, adapted from pr-2503) - OMPCODE isolation for cursor/kimi/secondmate harness tests and the shared test lib, so suites run correctly under an omp parent host - wire the delegation-shape subagent guard into the omp turn-end extension (both upstream OMP PRs omitted it)
…e reviewed designs - fm-harness.sh: grok marker precedes OMPCODE (cursor > grok > omp > claude > pi), matching the documented adapter precedence and the detection matrix - fm-session-lock-lib.sh: omp stays OUT of the harness tables; its lock identity is marker-gated (CLAUDECODE=1 on the self ancestry walk) only, per the reviewed session-lock design, not a bare name/path match - fm-omp-harness.test.sh: pin the marker-clean ancestry walk with a fake ps so the unknown verdict is deterministic when the suite itself runs under omp - fm-kimi-harness.test.sh: drop ambient OMPCODE in the detection cases
… evidence for omp 18.0.6 (2026-08-27 Linux)
- fm-session-start/fm-bootstrap: assert on MISSING: tasks-axi instead of MISSING: node, which a host's own /usr/bin/node silently satisfies - fm-bootstrap: skip the absent-CLI row for a backend whose real CLI sits in the run PATH (presence-only availability check), so hosts with /usr/bin/zellij do not fail a scenario they cannot reproduce
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.
Intent
Port full Oh My Pi (omp) support into the firstmate fork: detect omp before the Claude marker in fm-harness.sh, add the omp primary supervision stack (.omp/extensions watch + turn-end guard with the delegation-shape subagent guard), omp crewmate/scout dispatch in fm-spawn with busy-state/composer/control wiring, omp session-lock identity, supervision protocol and verification docs, and the omp test suites. Also sync the fork to upstream main and make MISSING-diagnostic test assertions host-deterministic.
What Changed
bin/fm-harness.shvia the unambiguousOMPCODEenv var and an exact process-name match, and treat omp as Claude-identified for session-lock ownership without misclaiming a foreign omp process's lock identity (bin/fm-session-lock-lib.sh,bin/fm-lock.sh)..omp/extensions(watcher bridge plus turn-end guard with the delegation-shape subagent guard), omp crewmate/scout dispatch inbin/fm-spawn.shwith busy-state, composer, and control wiring, and omp harness support inbin/fm-classify-lib.sh,bin/fm-composer-lib.sh,bin/fm-busy-lib.sh, andbin/fm-send.sh.Risk Assessment
✅ Low: The fix round is a single, well-bounded, verified-correct tightening of the omp args-matching pattern in fm-harness.sh that resolves the previously flagged substring false-positive (and the earlier composer-regex finding was explicitly ignored by the user), with no new risk introduced in the pipeline-authored code.
Testing
Exercised the whole omp port: live runs of both opt-in guards against the real omp v18.0.6 binary proved detection precedence, the session-lock identity (comm=omp, CLAUDECODE=1, ancestry-stopping recognition, foreign-pid/marker persistence guards), and the primary supervision stack (session_stop blocked + native continuation, fm_watch_arm_omp discoverable/callable, watcher retire/rearm), plus the fm-spawn omp launch template and real-spawn-generated omp-ext busy-state artifact through the actual writer and classifier; every portable omp-related suite (harness, session-lock-ancestry, extensions, busy-adapter-wiring, composer-lib, turnend-guard, supervision-instructions, subagent-pretool-check, bootstrap) passes with zero failures, both remaining skips being deliberate host-environment or determinism skips, and the worktree was left clean.
Evidence: omp detection matrix and spawn launch template
Source: omp detection matrix and spawn launch template
OMPCODE=1 CLAUDECODE=1 -> omp; CLAUDECODE=1 alone -> claude; PI marker alone -> pi; cursor/grok markers still win; fm-spawn omp template: omp --approval-mode yolo -e __OMPEXT__ ...Evidence: live omp session-lock identity guard (omp v18.0.6)
Source: live omp session-lock identity guard (omp v18.0.6)
ok - omp session-lock live guard: a real omp session's own process environment carries CLAUDECODE=1; a real omp process's own comm reports base name 'omp'; the real (unfaked) classifier recognizes a live omp process from its own ancestry without extending past itEvidence: live omp primary supervision guard (omp v18.0.6)
Source: live omp primary supervision guard (omp v18.0.6)
ok - omp omp/18.0.6: session_stop blocked and continued natively, and fm_watch_arm_omp was discoverable and callableEvidence: omp-targeted suite results
Source: omp-targeted suite results
fm-omp-harness PASS; fm-omp-primary-extensions 1 ok; fm-omp-primary-types skipped (package not resolvable on host); fm-session-lock-ancestry 13 oks; fm-busy-adapter-wiring 11 oks; fm-composer-lib 34 oks; fm-turnend-guard 70 oks; fm-supervision-instructions 10 oks; fm-subagent-pretool-check 14 oks; fm-bootstrap 28 oks (deterministic ambient-CLI skip)Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
bin/fm-harness.sh:119- The new*omp*) echo omparm in the node/python ancestry-args branch matches any argv0/args containing "omp" as a raw substring (e.g.python3 /opt/compare/...,node foo-comp/...). The author's own comment (lines 100-103) establishes that omp's liveps -o comm=is exactlyomp(caught by the precise exact-matchomp)arm at line 103), so this args substring arm is effectively dead for real omp and only adds false-positive surface. "omp" is a far commoner substring than the sibling patterns (claude/codex/opencode/grok), so a node/python process in the ancestry whose path contains it would be misidentified as omp, which then feeds busy-source, control, and session-lock classification. Consider matching a whole-wordomptoken (or dropping the args arm since omp never runs under a bare node/python interpreter), consistent with the exact-omp)precedent.bin/fm-composer-lib.sh:327-FM_DELIVERY_BUSY_REGEX_DEFAULT(the generic, harness-agnostic busy regex used for the''/unknown-harness case) gained⟨esc⟩. omp already gets its own dedicated regex (FM_DELIVERY_OMP_BUSY_REGEX_DEFAULT='⟨esc⟩') via theomp)arm, so adding the glyph to the shared default broadens busy detection for ALL unknown-harness consumers. Risk is low because the glyph is omp-specific, but any other harness TUI that renders the literal⟨esc⟩key-hint while idle would now read busy on the unknown path. Not a blocker; noting the deliberate widening.🔧 Fix: Tighten omp args detection to whole-word match in fm-harness.sh
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
tests/fm-omp-harness.test.sh- omp detection env-marker matrix (OMPCODE vs CLAUDECODE/PI/cursor/grok precedence, no-marker unknown, crew mirror) - PASSenv OMPCODE=1 CLAUDECODE=1 bin/fm-harness.sh-> omp;CLAUDECODE=1alone -> claude;CURSOR_AGENT=1/GROK_AGENT=1+ OMPCODE -> cursor/grok (live detection matrix)FM_OMP_LOCK_LIVE_E2E=1 tests/fm-omp-session-lock-live-e2e.test.sh- real omp v18.0.6 session reported CLAUDECODE=1 in its own bash-tool env, realpscomm=omp, and the real (unfaked)fm_harness_process_matchesrecognized its own ancestry without setting FM_HARNESS_IS_CLAUDE - PASSFM_OMP_PRIMARY_LIVE_E2E=1 tests/fm-omp-primary-live-e2e.test.sh- real omp v18.0.6 with the tracked .omp/extensions: session_stop forced bin/fm-turnend-guard.sh twice (initial + native continuation OMP_TURNEND_CONTINUED marker) and fm_watch_arm_omp started/retired the watcher child across session_shutdown - PASStests/fm-omp-primary-extensions.test.sh- node-driven guard/watcher extension logic: guard returns typed FIRSTMATE_OP continuation and never sendMessage; retire-then-rearm generation ordering - PASStests/fm-session-lock-ancestry.test.sh- 13 cases incl. omp Claude-identity gated on CLAUDECODE=1 marker (never bare name), foreign-omp pid isolation, persisted .lock.omp-claude record trusted over ambient marker, pid-reuse identity rejection, interrupted marker-write leaves no lock - PASStests/fm-busy-adapter-wiring.test.sh- runs REAL fm-spawn to generatestate/<id>.omp-ext.ts, then drives it with real fm-busy-event.sh + classifier: agent_start -> busy, agent_end willContinue -> busy, agent_end !ctx.isIdle() -> busy, genuine settle -> idle, turn_end stays a notification - PASSlaunch_template omp <kind>- emitsomp --approval-mode yolo -e __OMPEXT__ __MODELFLAG____EFFORTFLAG__"$(__OPINPUT__ encode launch-brief < __BRIEF__)"tests/fm-composer-lib.sh(34 oks, incl. capped omp composer shape),tests/fm-turnend-guard.test.sh(70 oks),tests/fm-supervision-instructions.test.sh(10 oks, omp snippet + repair line + ordinary-wake line),tests/fm-subagent-pretool-check.test.sh(14 oks, delegation-shape guard) - PASStests/fm-bootstrap.test.sh(28 oks) - MISSING-diagnostic assertions now use faked-only tasks-axi instead of real /usr/bin/node, and absent-CLI rows skip deterministically when an ambient zellij is in the run PATH; no-mistakes floor bumped to v1.46.0 - PASStests/fm-omp-primary-types.test.sh- SKIP by design: @oh-my-pi/pi-coding-agent not resolvable from npm root or the compiled omp binary on this host; the extension TS was instead type-loaded and executed by node (extensions suite) and by real omp v18.0.6 (live guard)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.