Compare commits

...

5 Commits

Author SHA1 Message Date
e827a10680 fix(haivemind): clear blank Textual Select
Some checks failed
CI / check (push) Has been cancelled
Textual 8 treats Select.BLANK as False, causing ConfigScreen to crash.
2026-08-20 14:37:26 +03:00
b8cb8908e7 fix(haivemind): restore deferred startup
Some checks failed
CI / check (push) Has been cancelled
Start engine only after MainScreen mounts, while preserving explicit MainScreen widget lookup.
2026-08-20 14:22:54 +03:00
e8b2dfda36 fix(haivemind): query MainScreen instance directly instead of app default screen
Some checks failed
CI / check (push) Has been cancelled
App.query_one searches the app's default screen (_default), but #chat-log/#health-bar/etc. live on the pushed MainScreen. Every HaivemindApp handler failed with NoMatches at runtime. Hold a reference to the MainScreen instance and query it directly so widget lookup no longer depends on which screen is active.
2026-08-20 13:38:39 +03:00
42016cda27 fix(haivemind): start engine after MainScreen mounts
Some checks failed
CI / check (push) Has been cancelled
2026-08-20 10:35:03 +03:00
2c5e64039a fix(haivemind): start engine after MainScreen mounts to avoid NoMatches on #chat-log
Upstream haivemind_tui.py started the engine synchronously in HaivemindApp.on_mount while MainScreen mounts asynchronously, causing ProbeStart handler to query #chat-log on the default screen (NoMatches). Move the start_run trigger into MainScreen.on_mount so it runs only once the screen is active.
2026-08-20 10:33:57 +03:00
3 changed files with 471 additions and 0 deletions

View File

@@ -1,3 +1,5 @@
{"_type":"issue","id":"nix-overlay-dv3","title":"Fix Config screen crash on F2","description":"F2 opens ConfigScreen then Textual raises InvalidSelectValueError because _refresh_aggregator_dropdown assigns Select.BLANK, resolved as False, instead of clearing selection.\\n\\nAcceptance: Config screen opens with saved consensus configuration; regression covers no selected aggregator; targeted tests pass.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-20T11:34:03Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-20T11:34:07Z","started_at":"2026-08-20T11:34:07Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"nix-overlay-a9m","title":"Fix haivemind startup after MainScreen mount","description":"Restore deferred engine startup while preserving MainScreen widget lookup fix.","status":"closed","priority":1,"issue_type":"bug","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-20T11:20:36Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-20T11:21:43Z","started_at":"2026-08-20T11:20:43Z","closed_at":"2026-08-20T11:21:43Z","close_reason":"Restored engine startup after MainScreen mount and verified package build.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"nix-overlay-q00","title":"Add haivemind package","description":"Package https://github.com/dev-boz/haivemind in the Nix overlay, document it, validate the build, and complete repository workflow.","status":"closed","priority":2,"issue_type":"task","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-08T16:32:37Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-08T16:36:41Z","started_at":"2026-08-08T16:33:00Z","closed_at":"2026-08-08T16:36:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"nix-overlay-q00","title":"Add haivemind package","description":"Package https://github.com/dev-boz/haivemind in the Nix overlay, document it, validate the build, and complete repository workflow.","status":"closed","priority":2,"issue_type":"task","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-08T16:32:37Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-08T16:36:41Z","started_at":"2026-08-08T16:33:00Z","closed_at":"2026-08-08T16:36:41Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"nix-overlay-67e","title":"Add traycer package","description":"Add new package for traycer from https://github.com/traycerai/traycer. Steps: research repo build system, create packages/traycer/, stage with git add, test nix build, update README.","status":"closed","priority":2,"issue_type":"task","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-07T08:59:25Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-07T09:22:07Z","started_at":"2026-08-07T09:00:29Z","closed_at":"2026-08-07T09:22:07Z","close_reason":"Traycer package added, built, pushed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"nix-overlay-67e","title":"Add traycer package","description":"Add new package for traycer from https://github.com/traycerai/traycer. Steps: research repo build system, create packages/traycer/, stage with git add, test nix build, update README.","status":"closed","priority":2,"issue_type":"task","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-07T08:59:25Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-07T09:22:07Z","started_at":"2026-08-07T09:00:29Z","closed_at":"2026-08-07T09:22:07Z","close_reason":"Traycer package added, built, pushed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"nix-overlay-2gf","title":"Update all packages to latest versions","description":"## Why\\nUser request: update every package in the overlay to its latest upstream version.\\n\\n## What\\nBump version + src hash (and cargoHash for Rust) in each packages/\u003cname\u003e/package.nix, update README table if needed, verify builds.\\n\\n## Acceptance\\n- Every package builds with nix build .#\u003cname\u003e\\n- README reflects new versions\\n- Changes committed and pushed","notes":"All packages updated and built successfully. Commit 1f3f0f468bdd on local main. PUSH BLOCKED: git.millerson.name:22002 and DoltHub unreachable from this network (SSH connect timeout). Re-run git push + bd dolt push when network is back, then close.","status":"closed","priority":2,"issue_type":"task","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-07T07:22:50Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-07T13:17:34Z","started_at":"2026-08-07T07:22:55Z","closed_at":"2026-08-07T13:17:34Z","close_reason":"All packages updated to latest versions, verified building, pushed to origin","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"nix-overlay-2gf","title":"Update all packages to latest versions","description":"## Why\\nUser request: update every package in the overlay to its latest upstream version.\\n\\n## What\\nBump version + src hash (and cargoHash for Rust) in each packages/\u003cname\u003e/package.nix, update README table if needed, verify builds.\\n\\n## Acceptance\\n- Every package builds with nix build .#\u003cname\u003e\\n- README reflects new versions\\n- Changes committed and pushed","notes":"All packages updated and built successfully. Commit 1f3f0f468bdd on local main. PUSH BLOCKED: git.millerson.name:22002 and DoltHub unreachable from this network (SSH connect timeout). Re-run git push + bd dolt push when network is back, then close.","status":"closed","priority":2,"issue_type":"task","assignee":"Alexander Miroshnichenko","owner":"alex@millerson.name","created_at":"2026-08-07T07:22:50Z","created_by":"Alexander Miroshnichenko","updated_at":"2026-08-07T13:17:34Z","started_at":"2026-08-07T07:22:55Z","closed_at":"2026-08-07T13:17:34Z","close_reason":"All packages updated to latest versions, verified building, pushed to origin","dependency_count":0,"dependent_count":0,"comment_count":0}

View File

@@ -0,0 +1,459 @@
--- a/haivemind_tui.py
+++ b/haivemind_tui.py
@@ -1197,0 +1198,8 @@
+ def on_mount(self) -> None:
+ app = self.app
+ if (
+ getattr(app, "_prompt", None) is not None
+ and getattr(app, "_config", None) is not None
+ ):
+ app.start_run(app._prompt, app._config)
+
@@ -1220,13 +1220,13 @@
self._engine: HaivemindEngine | None = None
self._run_started = False
self._run_start_time: float = 0.0
+ self._main_screen = None
def on_mount(self) -> None:
- self.push_screen(MainScreen())
+ self._main_screen = MainScreen()
+ self.push_screen(self._main_screen)
self.set_interval(0.15, self._tick_spinner)
- if self._prompt is not None and self._config is not None:
- self.start_run(self._prompt, self._config)
- else:
+ if self._prompt is None or self._config is None:
self.push_screen(ConfigScreen())
def start_run(self, prompt: str, config: HaivemindConfig) -> None:
@@ -1303,7 +1303,7 @@
def _tick_spinner(self) -> None:
_advance_spinner()
try:
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
if "streaming" in hbar._model_status.values():
hbar._refresh()
except NoMatches:
@@ -1315,20 +1315,20 @@
def _on_probe_start(self) -> None:
import time as _time
self._run_start_time = _time.time()
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
chat.add_summary("[bold yellow]Probing models...[/]")
if self._config is not None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_models(self._config.models)
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.consensus_info = "probing"
header.progress_pct = 0.0
@on(ProbeResult)
def _on_probe_result(self, event: ProbeResult) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "done" if event.ok else "failed")
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
if event.ok:
color = hbar.model_color(event.model)
chat.add_summary(f"[{color}][probe] ok: {event.model}[/]")
@@ -1339,44 +1339,44 @@
@on(ProbeDone)
def _on_probe_done(self, event: ProbeDone) -> None:
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
chat.add_summary(
f"[bold yellow]Probe complete:[/] {len(event.healthy)} healthy, "
f"{len(event.failures)} failed"
)
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.consensus_info = ""
if event.healthy:
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_models(event.healthy)
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.update_layout(event.healthy, hbar._model_colors)
@on(RoundStart)
def _on_round_start(self, event: RoundStart) -> None:
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
chat.add_summary(f"\n[bold cyan]\u2500\u2500 Round {event.round_idx}/{event.total} \u2500\u2500[/]")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.round_info = f"hAIvemind \u2502 Round {event.round_idx}/{event.total}"
header.consensus_info = ""
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.clear_all()
except NoMatches:
pass
@on(ModelStart)
def _on_model_start(self, event: ModelStart) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "streaming")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
total = len(hbar._model_status) if hbar._model_status else 0
done_count = sum(
1 for s in hbar._model_status.values() if s in ("streaming", "done")
)
header.model_info = f"Models: {done_count}/{total}"
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
active = [
m for m, s in hbar._model_status.items() if s != "dropped"
]
@@ -1386,27 +1386,27 @@
@on(ModelStream)
def _on_model_stream(self, event: ModelStream) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
color = hbar.model_color(event.model)
t = event.fragment.strip()
if t:
chat.add_summary(f"[{color}][{event.model}][/] {t}")
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.stream_to_model(event.model, event.fragment)
except NoMatches:
pass
@on(ModelDone)
def _on_model_done(self, event: ModelDone) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
ar = event.agent_round
if ar.error and ar.error not in ("stderr",):
hbar.set_status(event.model, "failed")
else:
hbar.set_status(event.model, "done")
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
color = hbar.model_color(event.model)
if ar.has_valid_draft:
chat.add_summary(
@@ -1419,14 +1419,14 @@
@on(ModelDropped)
def _on_model_dropped(self, event: ModelDropped) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "dropped")
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
chat.add_summary(
f"[red]\u26a0 {event.model} dropped:[/] {event.reason}"
)
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
remaining = [
m for m, s in hbar._model_status.items() if s != "dropped"
]
@@ -1436,7 +1436,7 @@
@on(RoundDone)
def _on_round_done(self, event: RoundDone) -> None:
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
dur = event.record.get("duration_s", 0)
chat.add_summary(
f"[dim]\u2500\u2500 end round {event.round_idx} ({dur}s) \u2500\u2500[/]"
@@ -1446,15 +1446,15 @@
best_sim = _compute_best_similarity(drafts)
threshold = self._config.threshold if self._config else 0.75
pct = min(1.0, best_sim / threshold) if threshold > 0 else 0.0
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.progress_pct = pct
header.consensus_info = f"sim: {best_sim:.2f}"
@on(Consensus)
def _on_consensus(self, event: Consensus) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- header = self.query_one("#header-bar", HeaderBar)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
total_models = len(hbar._model_status) if hbar._model_status else 0
import time as _time
elapsed = (
@@ -1493,9 +1493,9 @@
@on(Fallback)
def _on_fallback(self, event: Fallback) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- header = self.query_one("#header-bar", HeaderBar)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
total_models = len(hbar._model_status) if hbar._model_status else 0
header.consensus_info = "fallback"
header.progress_pct = 0.0
@@ -1518,30 +1518,30 @@
@on(EngineError)
def _on_engine_error(self, event: EngineError) -> None:
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
chat.add_summary(f"[red]Error:[/] {event.message}")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.consensus_info = "error"
@on(EngineLog)
def _on_engine_log(self, event: EngineLog) -> None:
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
chat.add_summary(event.message)
@on(MoaRefStart)
def _on_moa_ref_start(self, event: MoaRefStart) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "streaming")
color = hbar.model_color(event.model)
chat.add_summary(f"[{color}]\U0001f4e1 [ref] {event.model} analyzing...[/]")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.round_info = "hAIvemind \u2502 MoA \u2502 References"
@on(MoaRefDone)
def _on_moa_ref_done(self, event: MoaRefDone) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
- chat = self.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
color = hbar.model_color(event.model)
if event.err and not event.raw.strip():
hbar.set_status(event.model, "failed")
@@ -1552,32 +1552,32 @@
@on(MoaRefStream)
def _on_moa_ref_stream(self, event: MoaRefStream) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
color = hbar.model_color(event.model)
t = event.fragment.strip()
if t:
chat.add_summary(f"[{color}][ref:{event.model}][/] {t}")
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.stream_to_model(event.model, event.fragment)
except NoMatches:
pass
@on(MoaAggStart)
def _on_moa_agg_start(self, event: MoaAggStart) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "streaming")
color = hbar.model_color(event.model)
chat.add_summary(f"\n[bold {color}]\U0001f9e0 [aggregator] {event.model} synthesizing...[/]")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.round_info = "hAIvemind \u2502 MoA \u2502 Aggregating"
@on(MoaAggDone)
def _on_moa_agg_done(self, event: MoaAggDone) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
- chat = self.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
color = hbar.model_color(event.model)
if event.raw.strip():
hbar.set_status(event.model, "done")
@@ -1588,14 +1588,14 @@
@on(MoaAggStream)
def _on_moa_agg_stream(self, event: MoaAggStream) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
color = hbar.model_color(event.model)
t = event.fragment.strip()
if t:
chat.add_summary(f"[{color}][agg:{event.model}][/] {t}")
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.stream_to_model(event.model, event.fragment)
except NoMatches:
pass
@@ -1604,18 +1604,18 @@
@on(FusionPanelStart)
def _on_fusion_panel_start(self, event: FusionPanelStart) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "streaming")
color = hbar.model_color(event.model)
chat.add_summary(f"[{color}]\U0001f52c [panel] {event.model} analyzing...[/]")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.round_info = "hAIvemind \u2502 Fusion \u2502 Panel"
@on(FusionPanelDone)
def _on_fusion_panel_done(self, event: FusionPanelDone) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
- chat = self.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
color = hbar.model_color(event.model)
if event.err and not event.raw.strip():
hbar.set_status(event.model, "failed")
@@ -1626,32 +1626,32 @@
@on(FusionPanelStream)
def _on_fusion_panel_stream(self, event: FusionPanelStream) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
color = hbar.model_color(event.model)
t = event.fragment.strip()
if t:
chat.add_summary(f"[{color}][panel:{event.model}][/] {t}")
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.stream_to_model(event.model, event.fragment)
except NoMatches:
pass
@on(FusionJudgeStart)
def _on_fusion_judge_start(self, event: FusionJudgeStart) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "streaming")
color = hbar.model_color(event.model)
chat.add_summary(f"\n[bold {color}]\u2696\ufe0f [judge] {event.model} analyzing...[/]")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.round_info = "hAIvemind \u2502 Fusion \u2502 Judging"
@on(FusionJudgeDone)
def _on_fusion_judge_done(self, event: FusionJudgeDone) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
- chat = self.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
color = hbar.model_color(event.model)
if event.raw.strip():
hbar.set_status(event.model, "done")
@@ -1662,32 +1662,32 @@
@on(FusionJudgeStream)
def _on_fusion_judge_stream(self, event: FusionJudgeStream) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
color = hbar.model_color(event.model)
t = event.fragment.strip()
if t:
chat.add_summary(f"[{color}][judge:{event.model}][/] {t}")
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.stream_to_model(event.model, event.fragment)
except NoMatches:
pass
@on(FusionSynthStart)
def _on_fusion_synth_start(self, event: FusionSynthStart) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
hbar.set_status(event.model, "streaming")
color = hbar.model_color(event.model)
chat.add_summary(f"\n[bold {color}]\u270d\ufe0f [synth] {event.model} writing final answer...[/]")
- header = self.query_one("#header-bar", HeaderBar)
+ header = self._main_screen.query_one("#header-bar", HeaderBar)
header.round_info = "hAIvemind \u2502 Fusion \u2502 Synthesizing"
@on(FusionSynthDone)
def _on_fusion_synth_done(self, event: FusionSynthDone) -> None:
- hbar = self.query_one("#health-bar", ModelHealthBar)
- chat = self.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
color = hbar.model_color(event.model)
if event.raw.strip():
hbar.set_status(event.model, "done")
@@ -1698,14 +1698,14 @@
@on(FusionSynthStream)
def _on_fusion_synth_stream(self, event: FusionSynthStream) -> None:
- chat = self.query_one("#chat-log", ChatLog)
- hbar = self.query_one("#health-bar", ModelHealthBar)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
+ hbar = self._main_screen.query_one("#health-bar", ModelHealthBar)
color = hbar.model_color(event.model)
t = event.fragment.strip()
if t:
chat.add_summary(f"[{color}][synth:{event.model}][/] {t}")
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.stream_to_model(event.model, event.fragment)
except NoMatches:
pass
@@ -1720,7 +1720,7 @@
def action_toggle_theatre(self) -> None:
try:
- tc = self.query_one("#theatre-container")
+ tc = self._main_screen.query_one("#theatre-container")
if tc.has_class("visible"):
tc.remove_class("visible")
else:
@@ -1734,13 +1734,13 @@
def action_clear_theatre(self) -> None:
try:
- theatre = self.query_one("#theatre-container", TheatrePanel)
+ theatre = self._main_screen.query_one("#theatre-container", TheatrePanel)
theatre.clear_all()
except NoMatches:
pass
def action_show_artifacts(self) -> None:
- chat = self.query_one("#chat-log", ChatLog)
+ chat = self._main_screen.query_one("#chat-log", ChatLog)
if self._engine and self._engine.run_dir:
chat.add_summary(f"[bold]Artifacts:[/] {self._engine.run_dir}")
else:

View File

@@ -24,6 +24,16 @@ python3Packages.buildPythonApplication rec {
textual textual
]; ];
patches = [
./fix-chat-log-race.patch
];
postPatch = ''
substituteInPlace haivemind_tui.py \
--replace-fail "sel.value = Select.BLANK" "sel.clear()" \
--replace-fail "v is Select.BLANK" "v is Select.NULL"
'';
doCheck = false; doCheck = false;
pythonImportsCheck = [ pythonImportsCheck = [
"haivemind" "haivemind"