@agentikos/omega-os 0.19.40 → 0.19.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -188,7 +188,7 @@ from omega_engine.genesis import (
188
188
  )
189
189
  from omega_engine import plan as plan_v7
190
190
 
191
- __version__ = "0.19.40"
191
+ __version__ = "0.19.42"
192
192
 
193
193
  __all__ = [
194
194
  "__version__",
@@ -2989,14 +2989,43 @@ def cmd_menu(_args: argparse.Namespace) -> int:
2989
2989
  return 2
2990
2990
 
2991
2991
  if os.environ.get("TMUX"):
2992
- # We're already in a tmux session. Don't try to attach (tmux
2993
- # refuses nested attach). Tell the user how to switch.
2992
+ # We're already inside a tmux client. Nested `tmux attach` is
2993
+ # rejected by tmux, but `tmux switch-client` works fine — and is
2994
+ # what the user wants when they type `omega` from inside tmux.
2995
+ # v0.19.41 — actually DO the switch instead of just printing
2996
+ # instructions. Print is the v0.19.40 bug ("ça lance rien du tout").
2997
+ import subprocess
2994
2998
  if not tmux.is_alive(OMEGA_SESSION):
2995
2999
  tmux.spawn_omega_master(_omega_home())
2996
- print(f" Omega session is running. Switch to it with:")
2997
- print(f" tmux switch-client -t {OMEGA_SESSION}")
2998
- print(f" Or use Ctrl-b s to pick from the session list,")
2999
- print(f" or Ctrl-b z for the tmux-claude session manager.")
3000
+ # Are we already INSIDE the Omega session?
3001
+ proc = subprocess.run(
3002
+ ["tmux", "display-message", "-p", "#S"],
3003
+ capture_output=True, text=True,
3004
+ )
3005
+ current = (proc.stdout or "").strip()
3006
+ if current == OMEGA_SESSION:
3007
+ # Already in Omega — just jump to the menu window. If the
3008
+ # menu window was killed, recreate it.
3009
+ wins = subprocess.run(
3010
+ ["tmux", "list-windows", "-t", OMEGA_SESSION, "-F", "#W"],
3011
+ capture_output=True, text=True,
3012
+ )
3013
+ if "menu" not in (wins.stdout or "").split():
3014
+ subprocess.run(
3015
+ ["tmux", "new-window", "-t", OMEGA_SESSION, "-n",
3016
+ "menu", "omega menu-tui"],
3017
+ capture_output=True,
3018
+ )
3019
+ subprocess.run(
3020
+ ["tmux", "select-window", "-t", f"{OMEGA_SESSION}:menu"],
3021
+ capture_output=True,
3022
+ )
3023
+ else:
3024
+ # Different session — switch the client to Omega.
3025
+ subprocess.run(
3026
+ ["tmux", "switch-client", "-t", OMEGA_SESSION],
3027
+ capture_output=True,
3028
+ )
3000
3029
  return 0
3001
3030
 
3002
3031
  if not tmux.is_alive(OMEGA_SESSION):
@@ -589,10 +589,15 @@ bind-key Z display-popup -E -w 100% -h 100% "omega tmux menu"
589
589
  bind-key S choose-tree -Zs
590
590
 
591
591
  # Option+/ → session switcher (fzf popup, pick any live tmux session)
592
- # Option+z → Omega action menu (spawn Omega if missing + switch-client)
592
+ # Option+z → Omega action menu (popup direct like tmux-claude)
593
593
  # macOS Option key → enable "Use Option as Meta" in your terminal.
594
- bind-key -n M-/ display-popup -E -w 100% -h 100% "omega tmux switcher"
595
- bind-key -n M-z run-shell -b "tmux has-session -t Omega 2>/dev/null || tmux new-session -d -s Omega -n menu -c $HOME 'omega menu-tui'; tmux switch-client -t Omega"
594
+ #
595
+ # v0.19.42 both binds use absolute paths (substituted at write-time)
596
+ # because tmux's popup spawns a non-interactive shell that doesn't source
597
+ # .bashrc/.zshrc, so bare `omega` is not in PATH. The placeholder
598
+ # __OMEGA_BIN__ is replaced by write_default_config().
599
+ bind-key -n M-/ display-popup -E -w 100% -h 100% "__OMEGA_BIN__ tmux switcher"
600
+ bind-key -n M-z display-popup -E -w 100% -h 100% "__OMEGA_BIN__ menu-tui"
596
601
 
597
602
  # ════════════════════════════════════════════════════════════════════
598
603
  # Pixel/CRT amber theme (tmux-claude inspired)
@@ -720,8 +725,16 @@ bind-key r source-file ~/.tmux.conf \\; display-message "tmux.conf reloaded"
720
725
  # popup -E runs the command in a tmux popup overlay (modal). The popup
721
726
  # closes when the command exits, returning the user to their previous
722
727
  # pane. -w 100% -h 100% gives a full-screen popup (tmux-claude style).
723
- bind-key -n M-/ display-popup -E -w 100% -h 100% "omega tmux switcher"
724
- bind-key -n M-z run-shell -b "tmux has-session -t Omega 2>/dev/null || tmux new-session -d -s Omega -n menu -c $HOME 'omega menu-tui'; tmux switch-client -t Omega"
728
+ #
729
+ # v0.19.42 both binds use the ABSOLUTE PATH (substituted at write-time
730
+ # from __OMEGA_BIN__) because tmux display-popup spawns a non-interactive
731
+ # shell that doesn't source .bashrc/.zshrc — bare `omega` is not in PATH.
732
+ # Previously M-z used `run-shell -b` which executed in the background, so
733
+ # `tmux switch-client` ran in a forked process and never affected the
734
+ # foreground client. Result: "ça lance rien du tout". Fixed by going
735
+ # direct: display-popup the menu just like tmux-claude does.
736
+ bind-key -n M-/ display-popup -E -w 100% -h 100% "__OMEGA_BIN__ tmux switcher"
737
+ bind-key -n M-z display-popup -E -w 100% -h 100% "__OMEGA_BIN__ menu-tui"
725
738
 
726
739
  # ════════════════════════════════════════════════════════════════════
727
740
  # Pixel/CRT amber theme (tmux-claude inspired)
@@ -755,10 +768,23 @@ set -g mode-style "fg=default,bg=colour137"
755
768
  """
756
769
 
757
770
 
771
+ _OMEGA_BIN_PLACEHOLDER = "__OMEGA_BIN__"
772
+
773
+
774
+ def _resolve_omega_bin(omega_home: Path) -> str:
775
+ """Return the absolute path to the omega CLI binary for the given
776
+ OMEGA_HOME. Used to substitute the __OMEGA_BIN__ placeholder in the
777
+ bundled tmux configs so tmux's display-popup (which spawns a
778
+ non-interactive shell) doesn't have to rely on PATH propagation.
779
+ """
780
+ return str(omega_home / "Agentik_Tools" / "bin" / "omega")
781
+
782
+
758
783
  def write_default_config(
759
784
  target: str | Path | None = None,
760
785
  *,
761
786
  profile: str = "minimal",
787
+ omega_home: str | Path | None = None,
762
788
  ) -> Path:
763
789
  """Write a tmux config to ``target``.
764
790
 
@@ -773,52 +799,156 @@ def write_default_config(
773
799
  or replace theirs entirely. We never silently overwrite ``~/.tmux.conf``.
774
800
  Use :func:`install_into_home_tmux_conf` to do that explicitly + with
775
801
  backup.
802
+
803
+ v0.19.42 — substitutes ``__OMEGA_BIN__`` placeholder in the M-z /
804
+ M-/ keybinds with the absolute path of the omega CLI binary, so the
805
+ binds work even when tmux's display-popup shell doesn't have
806
+ ``$OMEGA_HOME/Agentik_Tools/bin`` in PATH.
776
807
  """
808
+ home = Path(omega_home or os.environ.get(
809
+ "OMEGA_HOME", str(Path.home() / "Omega")))
777
810
  if target is None:
778
- home = Path(os.environ.get("OMEGA_HOME",
779
- str(Path.home() / "Omega")))
780
811
  target = home / "Agentik_Tools" / "tmux.conf"
781
812
  target = Path(target)
782
813
  target.parent.mkdir(parents=True, exist_ok=True)
783
- content = _PRO_CONFIG if profile == "pro" else _DEFAULT_CONFIG
814
+ template = _PRO_CONFIG if profile == "pro" else _DEFAULT_CONFIG
815
+ content = template.replace(_OMEGA_BIN_PLACEHOLDER,
816
+ _resolve_omega_bin(home))
784
817
  target.write_text(content)
785
818
  return target
786
819
 
787
820
 
821
+ def _is_tmux_claude_config(text: str) -> bool:
822
+ """Detect whether a ``~/.tmux.conf`` already hosts a tmux-claude
823
+ install (github.com/agentik-os/tmux-claude). If so, we MUST NOT
824
+ overwrite it — tmux-claude's session-manager-v2.sh provides the
825
+ user's Alt+Z / Alt+/ session navigator already, and clobbering it
826
+ breaks their setup.
827
+
828
+ Markers checked:
829
+ * literal ``tmux-claude`` mention (their banner / source-file
830
+ comment)
831
+ * ``session-manager-v2.sh`` (their flagship menu script)
832
+ """
833
+ return ("tmux-claude" in text
834
+ or "session-manager-v2.sh" in text)
835
+
836
+
788
837
  def install_into_home_tmux_conf(
789
838
  *,
790
839
  profile: str = "pro",
791
840
  backup: bool = True,
792
841
  omega_home: str | Path | None = None,
842
+ force: bool = False,
793
843
  ) -> dict[str, str]:
794
844
  """Write the chosen config to ``~/.tmux.conf`` with a timestamped backup.
795
845
 
796
- Returns ``{written, backup_path, profile}``. Idempotent re-running
797
- keeps producing fresh backups so the operator can revert.
846
+ v0.19.42 RESPECTS tmux-claude. If ``~/.tmux.conf`` already hosts a
847
+ tmux-claude install, we DO NOT overwrite it. Instead we drop the
848
+ OmegaOS-specific keybinds at ``$OMEGA_HOME/Agentik_Tools/omega-tmux-add.conf``
849
+ and append one ``source-file`` line to ``~/.tmux.conf`` so both
850
+ coexist. Override with ``force=True``.
851
+
852
+ Returns:
853
+ * ``written`` — path of the file we wrote (~/.tmux.conf or the
854
+ add-on file)
855
+ * ``backup_path`` — backup of ~/.tmux.conf, empty if none
856
+ * ``bundled_copy`` — always written (the $OMEGA_HOME/Agentik_Tools/tmux.conf)
857
+ * ``profile`` — selected profile
858
+ * ``mode`` — "fresh" (we wrote a full conf) |
859
+ "preserved-tmux-claude" (we ADDED to the
860
+ existing tmux-claude conf without overwriting)
798
861
  """
799
862
  import shutil as _sh
800
863
  import time as _t
801
864
  home_conf = Path.home() / ".tmux.conf"
802
- backup_path = ""
803
- if home_conf.exists() and backup:
804
- ts = _t.strftime("%Y%m%d-%H%M%S")
805
- backup_path = str(home_conf.with_suffix(f".conf.bak.{ts}"))
806
- _sh.copy2(home_conf, backup_path)
807
- content = _PRO_CONFIG if profile == "pro" else _DEFAULT_CONFIG
808
- home_conf.write_text(content)
809
- # Also drop it under $OMEGA_HOME so `omega tmux install` is the
810
- # single source of truth that the engine sync mechanism can refresh.
811
- _ = write_default_config(omega_home=None) if False else None
812
865
  home = Path(omega_home or os.environ.get(
813
866
  "OMEGA_HOME", str(Path.home() / "Omega")
814
867
  ))
868
+ template = _PRO_CONFIG if profile == "pro" else _DEFAULT_CONFIG
869
+ content = template.replace(_OMEGA_BIN_PLACEHOLDER,
870
+ _resolve_omega_bin(home))
871
+
872
+ # Always update the bundled copy under $OMEGA_HOME.
815
873
  bundled = home / "Agentik_Tools" / "tmux.conf"
816
874
  bundled.parent.mkdir(parents=True, exist_ok=True)
817
875
  bundled.write_text(content)
876
+
877
+ backup_path = ""
878
+ if home_conf.exists() and backup:
879
+ ts = _t.strftime("%Y%m%d-%H%M%S")
880
+ backup_path = str(home_conf.with_suffix(f".conf.bak.{ts}"))
881
+ _sh.copy2(home_conf, backup_path)
882
+
883
+ # v0.19.42 — tmux-claude detection. If the user's ~/.tmux.conf is a
884
+ # tmux-claude install, leave it alone and provide an ADD-ON config.
885
+ if home_conf.exists() and not force:
886
+ existing = home_conf.read_text()
887
+ if _is_tmux_claude_config(existing):
888
+ addon_path = home / "Agentik_Tools" / "omega-tmux-add.conf"
889
+ addon_path.parent.mkdir(parents=True, exist_ok=True)
890
+ # Write a minimal OmegaOS-specific add-on: just the Ω bindings
891
+ # that don't collide with tmux-claude's M-z / M-/ (which we
892
+ # respect). Operator gets Omega via Ctrl-b z (prefix) or M-o.
893
+ addon = _omega_addon_config(home)
894
+ addon_path.write_text(addon)
895
+ # Append one source-file line if not already there.
896
+ source_line = f"source-file -q {addon_path}"
897
+ if source_line not in existing:
898
+ home_conf.write_text(existing.rstrip() + "\n\n"
899
+ + "# OmegaOS — Ω bindings (added "
900
+ + _t.strftime("%Y-%m-%d") + ")\n"
901
+ + source_line + "\n")
902
+ return {
903
+ "written": str(addon_path),
904
+ "backup_path": backup_path,
905
+ "bundled_copy": str(bundled),
906
+ "profile": profile,
907
+ "mode": "preserved-tmux-claude",
908
+ }
909
+
910
+ # Fresh install (or --force): write the full OmegaOS conf.
911
+ home_conf.write_text(content)
818
912
  return {
819
913
  "written": str(home_conf),
820
914
  "backup_path": backup_path,
821
915
  "bundled_copy": str(bundled),
822
916
  "profile": profile,
917
+ "mode": "fresh",
823
918
  }
824
- return target
919
+
920
+
921
+ def _omega_addon_config(omega_home: Path) -> str:
922
+ """The minimal OmegaOS keybinds + status-bar Ω indicator that we
923
+ can source from a tmux-claude config without conflict.
924
+
925
+ Conflicts with tmux-claude:
926
+ * M-z, M-/ → kept by tmux-claude (their session-manager-v2.sh)
927
+ * M-a → kept by tmux-claude (their AISB Command Center)
928
+
929
+ Free for OmegaOS:
930
+ * prefix+Ω → not bound by tmux-claude
931
+ * M-o → Alt+O for Omega menu (memorable)
932
+ * Ctrl-b Ω → prefix+Ω as an alternate
933
+ """
934
+ omega_bin = _resolve_omega_bin(omega_home)
935
+ return f"""# ════════════════════════════════════════════════════════════════
936
+ # OmegaOS — additive bindings (sourced from ~/.tmux.conf)
937
+ # This file is generated by omega_engine.tmux. Safe to source alongside
938
+ # any existing tmux-claude install; we only bind keys tmux-claude leaves
939
+ # free.
940
+ #
941
+ # Free keys we use:
942
+ # * Alt+O → Omega menu (popup, full-screen)
943
+ # * Ctrl-b o → same, via prefix
944
+ # * Ctrl-b Ω → status-bar indicator only
945
+ #
946
+ # Reserved by tmux-claude (we DO NOT touch):
947
+ # * Alt+Z, Alt+/, Alt+A
948
+ # ════════════════════════════════════════════════════════════════
949
+
950
+ # Alt+O → Omega menu (popup, full-screen)
951
+ bind-key -n M-o display-popup -E -w 100% -h 100% "{omega_bin} menu-tui"
952
+ # Ctrl-b o → same via prefix
953
+ bind-key o display-popup -E -w 100% -h 100% "{omega_bin} menu-tui"
954
+ """
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omega-engine"
3
- version = "0.19.40"
3
+ version = "0.19.42"
4
4
  description = "The Omega OS orchestration engine — event-sourced, verified-completion agent graphs."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -219,5 +219,99 @@ class TestAisbChat(unittest.TestCase):
219
219
  self.assertEqual(history[1].text, "ok, will look into it")
220
220
 
221
221
 
222
+ class TestBareOmegaAttachesNotPrints(unittest.TestCase):
223
+ """v0.19.41 — bug fix. `omega` (no args) used to PRINT instructions
224
+ instead of doing the switch when invoked from inside tmux. Lock in
225
+ that cmd_menu calls tmux switch-client / select-window directly."""
226
+
227
+ def test_cmd_menu_when_in_tmux_calls_switch_client(self):
228
+ """When $TMUX is set AND we're in a DIFFERENT session than Omega,
229
+ cmd_menu must run `tmux switch-client -t Omega` itself — not
230
+ print a hint and exit."""
231
+ import argparse
232
+ with mock.patch.dict("os.environ", {"TMUX": "/tmp/tmux-fake,1,0"}):
233
+ calls = []
234
+ class _FakeProc:
235
+ stdout = "some-other-session\n"
236
+ returncode = 0
237
+ def _fake_run(cmd, **kw):
238
+ calls.append(cmd)
239
+ return _FakeProc()
240
+ with mock.patch("subprocess.run", side_effect=_fake_run), \
241
+ mock.patch("shutil.which", return_value="/usr/bin/tmux"), \
242
+ mock.patch("omega_engine.tmux.is_alive", return_value=True):
243
+ from omega_engine.cli import cmd_menu
244
+ rc = cmd_menu(argparse.Namespace())
245
+ self.assertEqual(rc, 0)
246
+ switch_calls = [c for c in calls
247
+ if isinstance(c, list)
248
+ and len(c) >= 4
249
+ and c[:2] == ["tmux", "switch-client"]]
250
+ self.assertTrue(switch_calls,
251
+ f"cmd_menu must call `tmux switch-client -t Omega` when "
252
+ f"invoked from inside a different tmux session. "
253
+ f"Captured calls: {calls}")
254
+
255
+ def test_cmd_menu_when_already_in_omega_selects_menu_window(self):
256
+ """If the user is ALREADY in the Omega session, cmd_menu must
257
+ select the :menu window (not switch-client — that'd be a no-op)."""
258
+ import argparse
259
+ with mock.patch.dict("os.environ", {"TMUX": "/tmp/tmux-fake,1,0"}):
260
+ calls = []
261
+ class _ProcS:
262
+ stdout = "Omega\n"; returncode = 0
263
+ class _ProcWins:
264
+ stdout = "menu\naisb\nhermes\n"; returncode = 0
265
+ def _fake_run(cmd, **kw):
266
+ calls.append(cmd)
267
+ if cmd[:3] == ["tmux", "display-message", "-p"]:
268
+ return _ProcS()
269
+ if cmd[:3] == ["tmux", "list-windows", "-t"]:
270
+ return _ProcWins()
271
+ class R: returncode = 0; stdout = ""
272
+ return R()
273
+ with mock.patch("subprocess.run", side_effect=_fake_run), \
274
+ mock.patch("shutil.which", return_value="/usr/bin/tmux"), \
275
+ mock.patch("omega_engine.tmux.is_alive", return_value=True):
276
+ from omega_engine.cli import cmd_menu
277
+ rc = cmd_menu(argparse.Namespace())
278
+ self.assertEqual(rc, 0)
279
+ select_calls = [c for c in calls
280
+ if isinstance(c, list)
281
+ and len(c) >= 4
282
+ and c[:2] == ["tmux", "select-window"]
283
+ and "Omega:menu" in c]
284
+ self.assertTrue(select_calls,
285
+ f"cmd_menu must call `tmux select-window -t Omega:menu` "
286
+ f"when already inside Omega. Captured: {calls}")
287
+ switch_calls = [c for c in calls
288
+ if isinstance(c, list)
289
+ and len(c) >= 2
290
+ and c[:2] == ["tmux", "switch-client"]]
291
+ self.assertEqual(switch_calls, [],
292
+ f"cmd_menu must NOT switch-client when already in Omega.")
293
+
294
+ def test_cmd_menu_does_not_print_hint_when_in_tmux(self):
295
+ """REGRESSION — the v0.19.40 bug: cmd_menu printed
296
+ 'Omega session is running. Switch to it with: ...' instead of
297
+ actually switching."""
298
+ import argparse, io, sys as _sys
299
+ with mock.patch.dict("os.environ", {"TMUX": "/tmp/tmux-fake,1,0"}):
300
+ class _Proc:
301
+ stdout = "Other\n"; returncode = 0
302
+ with mock.patch("subprocess.run", return_value=_Proc()), \
303
+ mock.patch("shutil.which", return_value="/usr/bin/tmux"), \
304
+ mock.patch("omega_engine.tmux.is_alive", return_value=True), \
305
+ mock.patch.object(_sys, "stdout", new_callable=io.StringIO) as captured:
306
+ from omega_engine.cli import cmd_menu
307
+ cmd_menu(argparse.Namespace())
308
+ out = captured.getvalue()
309
+ self.assertNotIn("Switch to it with", out,
310
+ f"v0.19.40 regression: cmd_menu printed switch instructions "
311
+ f"instead of switching. Output: {out!r}")
312
+ self.assertNotIn("tmux switch-client -t Omega", out,
313
+ "cmd_menu must DO the switch, not print the command")
314
+
315
+
222
316
  if __name__ == "__main__":
223
317
  unittest.main(verbosity=2)
@@ -90,5 +90,112 @@ class TestTmuxClaudePalette(unittest.TestCase):
90
90
  )
91
91
 
92
92
 
93
+ class TestTmuxBindsFixedAndPathsAbsolute(unittest.TestCase):
94
+ """v0.19.42 — regression locks for the broken M-z bind that left the
95
+ user staring at a black screen ("ça lance rien du tout")."""
96
+
97
+ def test_m_z_uses_display_popup_not_run_shell_bg(self):
98
+ from omega_engine.tmux import _PRO_CONFIG, _DEFAULT_CONFIG
99
+ for name, cfg in (("_PRO_CONFIG", _PRO_CONFIG),
100
+ ("_DEFAULT_CONFIG", _DEFAULT_CONFIG)):
101
+ m_z_lines = [ln for ln in cfg.splitlines()
102
+ if ln.startswith("bind-key -n M-z")]
103
+ self.assertTrue(m_z_lines,
104
+ f"{name} must have bind-key -n M-z")
105
+ for ln in m_z_lines:
106
+ self.assertIn("display-popup", ln,
107
+ f"{name} M-z must use display-popup. Got: {ln}")
108
+ self.assertNotIn("run-shell -b", ln,
109
+ f"{name} M-z must NOT use `run-shell -b` (was the "
110
+ f"v0.19.40 bug — background switch-client breaks). "
111
+ f"Got: {ln}")
112
+
113
+ def test_omega_bin_placeholder_in_both_configs(self):
114
+ from omega_engine.tmux import _PRO_CONFIG, _DEFAULT_CONFIG
115
+ for name, cfg in (("_PRO_CONFIG", _PRO_CONFIG),
116
+ ("_DEFAULT_CONFIG", _DEFAULT_CONFIG)):
117
+ self.assertIn("__OMEGA_BIN__", cfg,
118
+ f"{name} must use __OMEGA_BIN__ placeholder for the "
119
+ f"absolute omega path substitution at write-time")
120
+
121
+ def test_write_default_config_substitutes_omega_bin(self):
122
+ import tempfile
123
+ from pathlib import Path
124
+ from omega_engine.tmux import write_default_config
125
+ with tempfile.TemporaryDirectory() as tmp:
126
+ home = Path(tmp) / "Omega"
127
+ home.mkdir()
128
+ written = write_default_config(profile="pro", omega_home=home)
129
+ content = written.read_text()
130
+ self.assertNotIn("__OMEGA_BIN__", content,
131
+ "placeholder must be substituted away")
132
+ self.assertIn(str(home / "Agentik_Tools" / "bin" / "omega"),
133
+ content,
134
+ "the absolute omega-bin path must appear in the conf")
135
+
136
+ def test_install_preserves_tmux_claude(self):
137
+ import tempfile
138
+ from pathlib import Path
139
+ from unittest import mock
140
+ from omega_engine.tmux import install_into_home_tmux_conf
141
+ with tempfile.TemporaryDirectory() as tmp:
142
+ tmp = Path(tmp)
143
+ fake_home = tmp / "fake-home"; fake_home.mkdir()
144
+ omega_home = tmp / "Omega"; omega_home.mkdir()
145
+ home_conf = fake_home / ".tmux.conf"
146
+ home_conf.write_text(
147
+ "# tmux-claude config\nset -g mouse on\n"
148
+ "bind -n M-z display-popup -E "
149
+ "/home/user/.tmux/scripts/session-manager-v2.sh\n"
150
+ )
151
+ with mock.patch.object(Path, "home", lambda: fake_home):
152
+ result = install_into_home_tmux_conf(
153
+ profile="pro", omega_home=omega_home,
154
+ )
155
+ self.assertEqual(result["mode"], "preserved-tmux-claude",
156
+ "install must detect tmux-claude and not overwrite")
157
+ content = home_conf.read_text()
158
+ self.assertIn("session-manager-v2.sh", content,
159
+ "tmux-claude bind must survive — we did NOT clobber")
160
+ self.assertIn("omega-tmux-add.conf", content,
161
+ "install must add a `source-file` line for OmegaOS add-on")
162
+ addon = omega_home / "Agentik_Tools" / "omega-tmux-add.conf"
163
+ self.assertTrue(addon.exists(),
164
+ "add-on conf must be written at "
165
+ "$OMEGA_HOME/Agentik_Tools/omega-tmux-add.conf")
166
+ addon_content = addon.read_text()
167
+ self.assertIn("M-o", addon_content,
168
+ "Omega add-on must bind M-o (Alt+O — a key tmux-claude "
169
+ "leaves free)")
170
+ # Must NOT rebind M-z (reserved by tmux-claude). The string
171
+ # 'M-z' may appear in COMMENTS, so check there's no actual
172
+ # bind-key directive on M-z.
173
+ bind_m_z_lines = [ln for ln in addon_content.splitlines()
174
+ if ln.lstrip().startswith("bind-key")
175
+ and "M-z" in ln]
176
+ self.assertEqual(bind_m_z_lines, [],
177
+ f"Omega add-on must NOT rebind M-z — found: {bind_m_z_lines}")
178
+
179
+ def test_force_overwrites_tmux_claude(self):
180
+ import tempfile
181
+ from pathlib import Path
182
+ from unittest import mock
183
+ from omega_engine.tmux import install_into_home_tmux_conf
184
+ with tempfile.TemporaryDirectory() as tmp:
185
+ tmp = Path(tmp)
186
+ fake_home = tmp / "fake-home"; fake_home.mkdir()
187
+ omega_home = tmp / "Omega"; omega_home.mkdir()
188
+ home_conf = fake_home / ".tmux.conf"
189
+ home_conf.write_text("# tmux-claude\nsession-manager-v2.sh\n")
190
+ with mock.patch.object(Path, "home", lambda: fake_home):
191
+ result = install_into_home_tmux_conf(
192
+ profile="pro", omega_home=omega_home, force=True,
193
+ )
194
+ self.assertEqual(result["mode"], "fresh",
195
+ "force=True must do a fresh overwrite")
196
+ self.assertNotIn("session-manager-v2.sh", home_conf.read_text(),
197
+ "force=True must clobber tmux-claude")
198
+
199
+
93
200
  if __name__ == "__main__":
94
201
  unittest.main(verbosity=2)
@@ -1 +1 @@
1
- 0.19.40
1
+ 0.19.42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentikos/omega-os",
3
- "version": "0.19.40",
3
+ "version": "0.19.42",
4
4
  "description": "Omega OS — installable agentic operating system with verified-completion orchestration. Event-sourced engine, 8-block rack, autonomous agents, MCP.",
5
5
  "bin": {
6
6
  "omega-os": "bin/omega-os.js"