Linux peer of packaging/freebsd/colibri_bridge.in: bridge the colibri-daemon
control-plane Unix socket to TCP 9190 on the Tailscale interface so mesh hosts
can reach the control plane.
- colibri-bridge.service: systemd unit running socat under sandboxing, BindsTo
the daemon, freebind so it can bind the tailnet IP before tailscaled is up.
- colibri-bridge.env.example: tunables (systemd parallel to the rc.d sysrc vars).
- colibri-bridge.nft: nftables ruleset for hosts WITHOUT ufw.
- README: install steps + the verified domedog host facts (tailnet IP, ufw
default-deny posture, 8443=CloudPanel and its public exposure) + open
questions for the cross-host (hermes) review.
Network gate already applied on domedog: `ufw allow in on tailscale0 to any
port 9190 proto tcp`. The systemd unit is proposed pending the hermes review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verify that toggling attention_only ON then OFF returns
filtered_panes() to the exact same list (order, count, IDs)
— no state corruption from the retain() partial filter.
agent-harness.md listed only zot's end-to-end proof (zot_rpc_smoke.rs,
ignored, ZOT_BIN-gated). pi now has better default CI coverage via
pi_spawn_live.rs (unignored, runs every test run), plus the new
default_agent_args unit tests proving the autospawn argv contract.
Also moves the autospawn argv reference into its own bullet for clarity.
Three unit tests for the autospawn contract function that determines
whether a spawned agent gets rpc-args (zot) or self-driving JSON args
(pi / everything else):
- default_agent_args_zot_gets_rpc — basename 'zot' → ["rpc"]
- default_agent_args_pi_gets_mode_json — basename 'pi' → ["--mode","json"]
- default_agent_args_unknown_gets_mode_json — safe default for
unknown harnesses (sample-pi-agent.py, colibri-test-agent)
Also covers path-prefixed variants (/usr/local/bin/zot, /usr/local/bin/pi)
to verify basename extraction works through the spawner pipeline.
This closes the untested gap: if someone adds a third harness or changes
the basename check, these tests catch the pi-path regression before it
reaches the operator who flips from zot to pi.
workspace green (0 failures).
Two new decisions captured, one page corrected:
terminal.md — the terminal-capability decision. Why colibri-tui and the agents
it supervises need modified-key reporting (Tab vs Shift-Tab, n vs N, Enter),
why the choice fell on Kitty, the tmux extended-keys + csi-u passthrough for
the in-tmux workflow, raw-vs-tmux distinction, the SSH xterm-kitty terminfo
gotcha, and pi's identical requirement. The decision is about capability;
Kitty is the instance.
operator-attention.md — the shipped attention system as one decision. Attention
as a derived view over the state machine (not a sixth variant), the TUI
bar/jump/filter/row-highlight, and the #193 terminal-capture + signature-triage
+ edge-triggered alerts. Records the has_attention session-filter bug and fix.
Lists what is still open (outbound push, answer-from-dashboard).
glasspane.md — corrected drift. The real AgentState enum is {Idle, Working,
Blocked, Done, Error}; Stalled is a derived flag, not a variant (the page's
diagram omitted Blocked and listed Stalled as a variant). The "Usability
roadmap (TODO)" listed the attention half as not-yet-built; it shipped via
#191/#193, so those items move to operator-attention.md and the roadmap keeps
only the genuinely-unbuilt direction.
index.md — two table rows (also satisfies the orphan-page check).
Verified: prettier-clean on all 4 files; wiki-lint --strict clean (144 pass /
0 fail, up from 137); no dangling refs, no orphans, no resurrected names.
(Sam & Claude)
scripts/ci-checks.sh runs five gates; .forgejo/workflows/ci.yml ran only four
— wiki-lint --strict was missing. quality-gates.md states "ci.yml encodes the
same checks" as local, which was not quite true. Add the wiki-lint step to the
markdown job so CI matches local the day a runner is registered.
wiki-lint is pure POSIX sh (grep/awk/sed/find), so it runs in the existing
node:20 container — no new image or job.
This does not by itself stop drift reaching main: as quality-gates.md notes,
no Forgejo Actions runner is registered, so nothing enforces CI server-side
today. The local pre-push hook remains the active enforcement layer; this
change ensures CI is ready to take over once a runner exists. Verified
wiki-lint passes clean on main (137 pass / 0 fail).
(Sam & Claude)
Once any pane carried a session_id, rebuild_session_list() forced
session_filter = Some(first), so the operator could never get back to the
aggregated "All sessions" view — Tab only cycled individual sessions.
Documented as a known bug in GLASSPANE-TUI-ENHANCEMENTS.md.
Model the session cycle as [All, s1, s2, ...]: index 0 is a synthetic "All
sessions" entry (filter = None), any other index scopes to sessions[i-1].
Two helpers encode the mapping:
- session_count() = sessions.len() + 1 (All is always present)
- apply_session_filter() maps session_idx -> filter (0 => None)
Behavior changes:
- On connect, the operator now lands on "All sessions" (was: the
alphabetically-first session). The aggregated view is the more useful
default and is always reachable via Tab/BackTab.
- The position indicator now shows for any cycle > 1 item, so the
"All (1 of 2)" hint appears even with a single session.
self.sessions still holds real session ids only (no sentinel string), so the
sorted/deduped invariant is unchanged.
Tests:
- rebuild_session_list_dedupes_and_sorts: updated for the new default +
offset mapping (index 1 => s1, index 2 => s2).
- all_sessions_view_is_reachable_with_sessions_present: new regression test
covering connect-defaults-to-All and the Tab cycle All -> s1 -> s2 -> All.
- attention_bar_ignores_other_session_panes: comment corrected (rebuild no
longer selects the first session).
19/19 TUI tests pass; fmt + clippy (-D warnings) clean.
(Sam & Claude)
cargo fmt --all --check failed on main with 5 drift sites in
crates/colibri-glasspane-tui/src/main.rs — all inside the tests added by #195,
which hand-packed Pane struct literals as `id: .., agent: .., state: ..,` on a
single line. rustfmt wants one field per line. Pure formatting: 91 insertions
/ 38 deletions, no logic change (only structural additions are field
continuations and one assert!(...) arg wrap).
Unblocks the fmt half of the workspace gate; second merged PR in a row (#193,
#195) to land fmt-red — worth tightening the CI fmt check as a required gate.
Verified: fmt clean workspace-wide, clippy -D warnings clean, 18/18 tests pass.
(Sam & Claude)
Four new tests closing the last attention-tier coverage gaps:
- jump_next_attention_skips_healthy_panes:
Panes [ok, err, ok, stalled, ok] — proves n jumps 0→1→3→wrap→1,
skipping healthy panes. Forward wrapping.
- jump_prev_attention_wraps_backwards:
Same layout — proves N jumps 4→3→1→wrap→3.
Backward wrapping.
- attention_bar_ignores_other_session_panes:
Error pane in session s2, viewing session s1 — bar must NOT
appear. Proves the filtered_panes()-based has_attention fix
from commit 4d95f11.
- jump_next_attention_reports_when_no_attention_panes:
All healthy panes — status message set to 'no attention',
selection unchanged.
18 tests, workspace green (0 failures).
Compute has_attention from filtered_panes() instead of the unfiltered
attention_count() so an error pane in another session no longer lights
the bar for the session you're viewing. Removes the now-unused
attention_count(). (The refinement from PR #194's feature commit that
was not in the version merged via #191.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scheduler_prompt_injection, cache_warming_enabled, and headroom_enabled
used env_parse::<bool>, i.e. bool::from_str, which accepts only "true"/
"false". Any other truthy spelling (1/yes/on/TRUE) silently parsed to
false — the feature failed closed with no error or log. Switch them to
the same env_bool helper added for terminal capture so =1/yes/on now work
as operators expect. Backward compatible: true/false keep their meaning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the screen-scraping half of Glasspane to complement its event-state
model. Ports the *brain* of the clawdie-ai tmux-screenshot skill (not the
PNG) into the Rust core and wires it into the daemon.
colibri-glasspane:
- terminal.rs: strip_ansi, content-hash frame ids (sha256[:12]),
CapturedFrame, and TerminalRecorder — a deduped ring buffer that drops
frames identical to the previous one (so polling a static pane collapses
to a log of real transitions) with edge-triggered alerting (a failure
fires once on its rising edge, re-fires only after it clears). Thin
capture_tmux_pane seam keeps I/O out of the testable core.
- signatures.rs: data-driven Severity/Signature/Detection/SignatureSet
matcher with a high-value linux_default() set (systemd/oom/disk/docker/
forwarding). Per-OS set is the hook for capability-routing.
colibri-daemon:
- DaemonState.terminal map of per-pane recorders; poll tick in run_loop
gated on COLIBRI_TERMINAL_CAPTURE, seeded from COLIBRI_TERMINAL_WATCH.
- capture_and_record() shares the blocking tmux capture (on spawn_blocking)
+ brief lock fold between the loop and the socket; env-gated Telegram
alert routing that no-ops cleanly when unconfigured.
- socket cmds: terminal-watch/unwatch/list/history/poll.
- env_bool helper: forgiving truthy parsing (1/true/yes/on) so
COLIBRI_TERMINAL_CAPTURE=1 is not silently false like bool::from_str.
Tests: 17 new glasspane unit tests + daemon socket/config tests; whole
workspace green, clippy clean. Verified live on Linux (domedog): autonomous
loop deduped ~5 ticks into 2 frames and fired one edge-triggered alert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
needs_attention() = Error + Blocked + Stalled (free function, single
source of truth). Includes Blocked because glasspane doc comments say
Blocked = 'operator attention needed' (queue_update / pending steering).
Tier 1 — Attention bar:
Red-bordered panel with '⚠ ATTENTION (N)' title replaces the header
when any pane needs attention. Shows pane id, reason, and agent.
Tier 2 — Jump keys (n/N):
n = next attention pane, N = previous (wrapping). Respects session
scope via filtered_panes(). Detail pane follows the jump.
Tier 3 — Attention filter (a key):
Toggles attention_only on App. Composes with session filter.
Tier 4 — Row highlight:
Attention rows get red background when unselected, inverted
dark-gray+light-red+bold when selected. Global row_highlight
neutralized.
Also:
- fix(tui): remove hardcoded dark-terminal assumptions — theme-agnostic
- fix(tui): force crossterm color output — override NO_COLOR=1 inherited
from Hermes sessions (crossterm honours no-color.org standard)
Adds colibri_daemon_require_secured knob (default NO). When enabled, the
daemon refuses to autospawn an agent until /var/db/colibri/.secured exists.
This interlock pairs with the clawdie-iso firstboot password gate (#139):
the gate writes .secured after the operator sets passwords, the daemon
reads it to gate autospawn + node_register.
Must run AFTER the provider.env block — otherwise COLIBRI_AUTOSPAWN=YES
from provider.env would override the NO set here. Defaults to NO so
deployed/disk hosts (which never run the firstboot gate) are unaffected.
Paired with: clawdie-iso PR #139 (force-root-password-on-first-boot).
The rc.d drops privilege via su -m, which preserves the environment from
/etc/rc (HOME=/). Without an explicit ZOT_HOME, zot resolves to
/.local/state/zot — missing any AGENTS.md installed by the seed importer.
Pin ZOT_HOME to /var/db/colibri/.local/state/zot. The seed importer
(clawdie-iso) targets this same path, so AGENTS.md placed on the seed
reaches the autospawned zot's global slot.
- §2: list colibri-mcp instead of colibri-test-agent (matches preflight at
build.sh:335 — test-agent is optional, gated by COLIBRI_STAGE_TEST_AGENT)
- §3: name the specific binaries preflight checks
- Notes: add Node.js (npm) to host toolchain requirements — build_and_stage_docs
needs node+npm, and the handoff should match REQUIREMENTS.md
geodesic-dome-mcp imports numpy + PIL at module load (not stdlib-only, as
#178 incorrectly stated). A present python3 therefore proves nothing — the
preflight would pass on a host missing numpy/Pillow and the tool would fail
only when the MCP host first invokes it.
- setup-mother.sh: add a 'python3 -c "import numpy, PIL"' check after the
python3-exists check, with a pkg install py311-numpy py311-pillow hint.
- MOTHER-SETUP.md: correct the prereq from 'stdlib only, no pip' to
'python3 + numpy + Pillow'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three reinforcing changes so the next agent's mother setup lands instead
of failing late:
- setup-mother.sh: fail-fast preflight for python3 (geodesic-dome-mcp is a
python3 script that otherwise installs fine and fails only when invoked).
- MOTHER-SETUP.md: new Prerequisites section — python3 on PATH, and the
COLIBRI_AUTOSPAWN_RPC_PROMPT boot decision (set = auto-spawn agent on
boot; unset = quiet token-free boot).
- FREEBSD-BUILD-LANE-HANDOFF.md: pointer to MOTHER-SETUP.md/setup-mother.sh
so the mother docs are discoverable from the build-lane entry point.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The handoff named v0.2.29 while clawdie-iso build.sh preflight defaulted
to v0.2.42. Pin to the current latest zot tag (v0.2.47) so the agent
builds the intended version and all references agree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The wiki-expansion move left the top-level README pointing at the old
docs/HEADROOM-SIDECAR.md path. wiki-lint only scans docs/wiki, so this
slipped through; repoint to docs/wiki/headroom-sidecar.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>