merge: catch up with upstream kunchenguid/firstmate (41 commits, 24 conflicts resolved) #2
Loading…
Reference in a new issue
No description provided.
Delete branch "fm/fm-upstream-merge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Catches this fork up with its parent,
kunchenguid/firstmate, by mergingupstream/mainintomain.main=8576b4f,upstream/main=70aeba8, merge-base4ee4a0aupstreamis read-only and was never written toWhat came from upstream
41 commits. The ones that matter for a single-home setup:
d0461e4cf951127ef26c44b6b89d345de4e3b6ee03bb352e7,bf01a424a9979a,ef2c3a28387039,3089a5771f0b3fPlus features: remote secondmate homes, per-task trace-context propagation, the Muse Code crewmate adapter,
fm-send --resolve-key, and the X-mode-to-Relay documentation rename. New libraries land intact (fm-startup-network.sh,fm-line-cap-lib.sh,fm-sessionstart-run.sh,fm-timeout-lib.sh,fm-nm-run-lib.sh,fm-trace-context-lib.sh,fm-secondmate-parent-lib.sh).What was ours, and survives
All seven core OMP files land verbatim - upstream never touched them:
bin/fm-backend.sh,bin/fm-supervise-daemon.sh,bin/fm-tmux-lib.sh,bin/fm-omp-primary-marker-lib.sh,bin/fm-primary-watch-core.ts,bin/fm-primary-watch-version-lib.sh,bin/backends/orca.sh.Also preserved: the locale-independent composer glyph strip (
a73484c), the committed-but-never-pushed teardown refusal (97359ee, still at step 1 of teardown, ahead of upstream's new reaping at step 2), the watcher signal-signature work (d136e7a/a2f23e6), the watcher ownership-across-rebinds fix (8a1054b), and the skills import (a5ec819).The five conflicts that needed judgement
Roughly 18 of the 24 were additive collisions - we added
ompto a list, upstream addedmuseto the same list - and are unioned. Both names are kept everywhere. These five were not mechanical.R1
bin/fm-composer-lib.sh- two different bugs on the same linesWe had refactored the leading-glyph strip into per-glyph arms because
${content#??}counts characters, so under a non-UTF-8 locale a multibyte glyph like❯(3 bytes) is miscounted and the strip corrupts the row. Upstream did not fix that; it added Muse's⟩to the same locale-fragile form.Taking either side alone loses something real. Resolved by adding
⟩to the per-glyph arms:Upstream's two bare-glyph
'❯'|'›'|'⟩')cases and its luminance comments merged cleanly and are kept, as is ourfm_composer_terminal_width.R3
bin/backends/tmux.sh- the dangerous oneUpstream rewrote agent liveness around a new
fm_backend_tmux_classify_process_namethat recognises no interpreter names. An OMP worker's process name isbun(oromp), so taking upstream wholesale classifies a live OMP worker asambiguous. A false negative here is the one verdict that can launch a duplicate agent onto a live worktree, and OMP support is this fork's reason for existing.Simply adding
bun|ompto the classifier is not sufficient either: a barebunshell is ordinary, and that would report a live agent in an empty pane.The resolution keeps upstream's new two-source probe (
classify_process_name,foreground_comms,foreground_argv0s- they genuinely harden liveness for the other harnesses and add Muse) and restores theexpected_bun/expected_ompparameters upstream dropped, dispatchingbun/ompto our existingfm_backend_tmux_bun_agent_statefrom both name sources:That helper proves a real OMP launch from the task-bound Bun/OMP identity - argv shape plus the verified executable - not from the name. An unproven
bunnever settles a verdict on its own; it leaves the paneambiguous, which is the conservative direction, because onlydeadauthorizes recovery.The audit that preceded this work explicitly did not implement R3 and flagged it UNVERIFIED, so it is implemented and tested here.
Test evidence.
tests/fm-tmux-agent-liveness.test.sh(upstream's new real-process, real-tmux suite) gains three OMP cases:bun <omp-entrypoint>launch, driven throughfm_backend_agent_statewith a real task meta file, classifiesalivebunshell with the same recorded identity staysambiguousambiguous, neveraliveplus a guard that
bunandompare not accepted as agent names on their own, so the positive case cannot pass for the wrong reason.The regression is genuinely covered: with the dispatch removed, the first case reports
ambiguousinstead ofaliveand the suite fails. With it restored, all 14 assertions pass.Those committed cases use a stand-in interpreter so they run anywhere. They were additionally confirmed against the real Bun runtime (
bun 1.3.14, the same binary the installedomp 17.2.6runs under), in a throwaway tmux lab drivingfm_backend_agent_statethrough a real task meta file:Both panes report
pane_current_command=bun, so the name alone is identical between them; only the argv shape and the/proc/<pid>/exeidentity separate the live worker from the bare shell, which is exactly the distinction this resolution restores.R4
bin/fm-spawn.sh- ordering in the secondmate launch pathOur OMP secondmate session prep (primary-extension marker plus session-lock PID validation) now runs before upstream's
FM_SKIP_SECONDMATE_INHERIT-guarded inheritance block, so a corrupt OMP home fails before foreign config is pushed into it. Our older unguarded inheritance lines are superseded by upstream's guarded form, which addsFM_CONFIG_INHERIT_LIVE=1.The
LAUNCHstring carries both sides: ourFM_OMP_SESSION_POINTERand upstream'sFM_TRACE_CONTEXTplusFM_SUPERVISION_MODEL. For an OMP secondmate the supervision model resolves topersistent, which is correct - OMP runs a persistent watcher.R5
bin/fm-teardown.sh- a line-continuation trapBoth sides added artifact paths to the same two
rm -flists (ours.omp-ext.ts/.omp-ready/.omp-started, upstream's.muse-session/.muse-session-current/.open-decisions-cursor). A careless union drops a trailing backslash and bash then tries to execute the next path. Resolved as onerm -fper block with all artifacts and correct continuations.Our unpushed-work refusal is untouched and still runs at step 1, ahead of upstream's new run-abort and process-reaping at step 2, so an undelivered worktree is still refused before anything is reaped.
R6
.agents/skills/harness-adapters/SKILL.md- delete versus modifyUpstream deleted the per-harness session-start bullet list from the skill and relocated that content. We had extended that list with an
ompbullet. The deletion is kept - the relocation is intentional and preserves a single owner - and the OMP fact is migrated into the relocated owner,docs/sessionstart-nudge.md, as a row in its harness-transport table.The fixture bump that had no conflict marker
Upstream raised
FM_TASKS_AXI_MINfrom0.1.1to0.2.4inbin/fm-tasks-axi-lib.sh, which merged cleanly. That silently put the faketasks-axiintests/fm-teardown.test.shbelow the floor, so the scout completion gate refused a teardown the test expects to succeed - anexit 0assertion getting1, with no conflict to draw attention to it. Fixed with the one-line fixture bump to0.2.4. Real teardowns were never affected; the installedtasks-axiis already0.2.4.Test results
bin/fm-lint.shclean (ShellCheck 0.11.0, pinned).bin/fm-test-run.sh --check-coverageok (139 suites).Full run -
bin/fm-test-run.sh --all:2,334 individual assertions passed. Every one of the 8 failing suites is accounted for below: none is caused by this merge, and each claim is backed by a run rather than an assumption - each failing suite was additionally re-run on its own against clean
mainor pureupstream/main, which is stronger evidence than any single sweep.Suites specifically re-run because the audit could not verify them:
fm-tmux-agent-liveness(incl. new OMP cases)fm-watcher-lockfm-omp-primary,fm-omp-harness,fm-omp-secondmatefm-teardown(all 7 unpushed-refusal tests + scout)fm-composer-lib,fm-composer-ghostfm-guard-stale-banner(upstreamd0461e4)fm-backend,fm-send-strict,fm-crew-state,fm-secondmate-livenessRemaining failures, all proven not merge-induced
fm-session-start.test.shandfm-bootstrap.test.sh- "MISSING: node" (environmental).Both assert that bootstrap reports
MISSING: nodeafter the fixture removesnodefrom a fake PATH prefix. This machine has/usr/bin/node, which is still on the tests'BASE_PATH(/usr/bin:/bin:/usr/sbin:/sbin), so the tool is found and no diagnostic appears. Proof: with a node-free base path both suites pass completely.fm-session-startadditionally fails identically on cleanmain(8576b4f) before this merge, andfm-bootstrap's assertion is new from upstream and fails identically on pureupstream/main.fm-backend-herdr-presentation-e2e.test.sh- "multi-home teardown changed active workspace/tab" (inherited).Fails identically on pure
upstream/main, with the same values (w3/w3:t1towY/wY:t1). The merged file is byte-identical to upstream's. This machine runs its sessions inside a real Herdr, and upstream's3089a57turned presentation spaces default-on, so the focus-drift assertion is exercised against ambient Herdr state.fm-calm-pi-extension.test.sh- "/export did not complete while calm mode was on" (pre-existing).A live Pi TUI case driving a real
/exportthrough tmux. Fails identically on cleanmain(8576b4f) before this merge, with the same message.fm-documentation-audiences.test.sh- "repository documentation audience check failed" (pre-existing).bin/fm-doc-audience-check.shreports seven unclassified skills, all from the local skills import ina5ec819:before-building,handoff,next-decision,pi-custom-model,to-spec,to-tickets,wayfinder. Identical output and exit code on cleanmain. Classifying those imported skills is separate work, out of scope for a merge.fm-on.test.shandfm-remote-doctor.test.sh- the remote-secondmate preflight (inherited).Both are upstream-only suites from the remote-secondmate feature (
7809ab9,733a504), absent from cleanmain, and byte-identical here to upstream's copies. Both fail identically on pureupstream/main:fm-on's failure is the same environmental class as thenodecases above: it asserts thatherdr,tasks-axi,treehouse, andharnessdo not resolve on PATH, and this machine has all four installed. This home has no remote secondmates, so the feature these suites cover is unused here.fm-watcher-lock.test.sh- "arm did not exit with HUP status (got 124)" (load flake, not a defect).124is the test's ownwait_for_exittimeout sentinel, not a process exit code: the armed watcher did not shut down within the ~8s the case allows, on a machine that was running the full 139-suite sweep concurrently with several standalone baseline runs.The case is provably untouched by this merge.
test_arm_hup_cleans_child_and_temp_outputis byte-identical in localmain,upstream/main, and the merged tree, andbin/fm-watch-arm.shandbin/fm-watch.share unchanged by the merge. My conflict resolution in this file touched onlytest_watch_restart_attaches_to_healthy_peer, a different case.Re-run twice on an idle machine, the suite is green both times:
EXIT=0, 30/30 assertions, 0 failures.One opt-in suite that could not run, on either side
tests/fm-omp-worker-tmux-live-e2e.test.sh(opt-in,FM_OMP_TMUX_LIVE_E2E=1) refuses before it starts:ship spawns require --mode <no-mistakes|direct-PR|local-only>. The file is byte-identical to cleanmainand passes no--mode, soab89395carried the per-task delivery contract into the other OMP spawn cases but missed this one. It fails the same way on cleanmain, so it is pre-existing and unrelated to this merge; fixing it is out of scope here and worth its own change.Patched locally in a throwaway clone just to see past that gate, it then stops at
OMP initial instruction was not acknowledged by a turn_start event, which needs live model credentials this environment does not have. That is why R3 was validated with the real-Bun lab described above rather than through this suite.Scope
Merge resolution only. The two live defects upstream does not fix - the away-mode injection wedge and the missing Forgejo support in the PR merge poll - are tracked separately and deliberately untouched here. The stale
dnthremote is left alone.Independent verification by firstmate
The full suite was re-run against this branch with
FM_HOMEcorrectly scoped to the tree under test: 2290 passing, 9 failing. Every failure was chased to ground rather than waved off, and none is merge-induced:fm-turnend-guardfm-watcher-lockfm-bootstrapnodeabsent; this machine has/usr/bin/nodefm-session-startMISSING-tool causefm-onupstream/mainfm-remote-doctorupstream/mainfm-backend-herdr-presentation-e2eupstream/main— sessions run inside a real herdrfm-calm-pi-extensionmain— pre-existingfm-documentation-audiencesmain— pre-existingTwo notes on method, because both nearly produced a wrong answer:
fm-onandfm-remote-doctordo not exist onmain, so a naive main-versus-branch comparison reported "0 failures on main" and looked like a regression. The meaningful counterfactual was pureupstream/main, where they fail the same way.fm-arm-pretool-checkcase A13 failed only becauseFM_HOMEwas left pointing at the primary checkout while testing a worktree. Repeating it three times made a harness error look deterministic. It passes withFM_HOMEscoped correctly or unset.* fix(bin): classify tmux agent liveness independent of process titles `fm_backend_tmux_agent_state` attributed a pane solely from `#{pane_current_command}`, which is a process TITLE a harness can rewrite, not a structural fact. Claude Code 2.1.220 reports its version string there, so a live Claude endpoint classified `ambiguous`: the session-start secondmate liveness sweep could no longer see it, and any consumer that gates on a positive classification refuses outright. Read a second, independent name source: the kernel `comm` of every process in the pane tty's foreground process group. Either source naming a verified harness yields `alive`, because a false `dead` is the one verdict that can start a duplicate agent on a live worktree. Scoping to the foreground process group rather than the pane's descendants keeps a harness-named background process from faking an agent, and covers multi-process launchers (the Pi Launcher path) without a special case. Verified on 2026-08-03 against all seven adapters running for real on tmux 3.6a / macOS 26.5.2 arm64: claude 2.1.220, codex-cli 0.146.0, opencode 1.18.11, pi 0.82.0, pi-signed 0.82.0, grok 0.2.118, kimi 0.31.1 all classify `alive`, each attributed by a source independent of its title. Two tests, because they fail for different reasons: - tests/fm-tmux-agent-liveness.test.sh pins the logic with real processes and no harness, so it runs everywhere CI runs tmux. It drives the two name sources apart on purpose and asserts the divergence, so no case can go quietly vacuous. - tests/fm-harness-liveness-drift-live-e2e.test.sh relaunches every installed harness and fails naming the harness and version when one stops being attributed by a title-independent source. AGENTS.md section 4 carries the resulting standing rule, and firstmate-coding-guidelines owns how to satisfy it. * no-mistakes: apply CI fixes * docs: move the harness-dependent-check policy out of AGENTS.md The standing rule was stated in AGENTS.md section 4 with the mechanics in firstmate-coding-guidelines, which split one contract across two owners and charged every session for a rule that only fires when firstmate's own harness-dependent code is being changed. firstmate-coding-guidelines is now the single owner of both the rule and how to satisfy it: real-harness proof required, that proof authorized to spend tokens, structural signals preferred over vendor-rendered surfaces, and a guard that fails loudly naming the harness and version where a surface signal is unavoidable. No inline stub is left behind, because AGENTS.md already carries the load trigger for that skill in sections 7 and 13, so it is read before any change to firstmate's shared tracked material. Also records the cross-platform lesson the pipeline caught in the portable regression, and corrects that file's header: the divergence assertion lives on the version-string case, which diverges on both supported platforms, rather than on every case. * no-mistakes(review): Harden tmux liveness identity and drift validation * no-mistakes(document): Clarify cross-platform tmux liveness documentation