@agentikos/omega-os 0.19.38 → 0.19.40

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.
Files changed (54) hide show
  1. package/bootstrap/lib/common.sh +19 -10
  2. package/bootstrap/templates/aisb/architect.md +27 -1
  3. package/bootstrap/templates/aisb/construct.md +27 -1
  4. package/bootstrap/templates/aisb/keymaker.md +27 -1
  5. package/bootstrap/templates/aisb/link.md +27 -1
  6. package/bootstrap/templates/aisb/lmc-protocol.md +27 -1
  7. package/bootstrap/templates/aisb/merovingian.md +27 -1
  8. package/bootstrap/templates/aisb/morpheus.md +27 -1
  9. package/bootstrap/templates/aisb/neo.md +27 -1
  10. package/bootstrap/templates/aisb/niobe.md +27 -1
  11. package/bootstrap/templates/aisb/oracle.md +27 -1
  12. package/bootstrap/templates/aisb/pythia.md +36 -0
  13. package/bootstrap/templates/aisb/seraph.md +27 -1
  14. package/bootstrap/templates/aisb/smith.md +27 -1
  15. package/bootstrap/templates/aisb/zion.md +27 -1
  16. package/omega/Agentik_Engine/omega_engine/__init__.py +1 -1
  17. package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
  19. package/omega/Agentik_Engine/omega_engine/__pycache__/paperclip_bridge.cpython-313.pyc +0 -0
  20. package/omega/Agentik_Engine/omega_engine/__pycache__/prompt_audit.cpython-313.pyc +0 -0
  21. package/omega/Agentik_Engine/omega_engine/__pycache__/tmux.cpython-313.pyc +0 -0
  22. package/omega/Agentik_Engine/omega_engine/__pycache__/tui.cpython-313.pyc +0 -0
  23. package/omega/Agentik_Engine/omega_engine/cli.py +39 -0
  24. package/omega/Agentik_Engine/omega_engine/paperclip_bridge.py +110 -0
  25. package/omega/Agentik_Engine/omega_engine/prompt_audit.py +395 -0
  26. package/omega/Agentik_Engine/omega_engine/tmux.py +61 -26
  27. package/omega/Agentik_Engine/omega_engine/tui.py +293 -86
  28. package/omega/Agentik_Engine/pyproject.toml +1 -1
  29. package/omega/Agentik_Engine/tests/__pycache__/test_install_ux.cpython-313-pytest-8.4.2.pyc +0 -0
  30. package/omega/Agentik_Engine/tests/__pycache__/test_install_ux.cpython-313.pyc +0 -0
  31. package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313-pytest-8.4.2.pyc +0 -0
  32. package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313.pyc +0 -0
  33. package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313-pytest-8.4.2.pyc +0 -0
  34. package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313.pyc +0 -0
  35. package/omega/Agentik_Engine/tests/__pycache__/test_tmux_palette.cpython-313-pytest-8.4.2.pyc +0 -0
  36. package/omega/Agentik_Engine/tests/__pycache__/test_tmux_palette.cpython-313.pyc +0 -0
  37. package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313-pytest-8.4.2.pyc +0 -0
  38. package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313.pyc +0 -0
  39. package/omega/Agentik_Engine/tests/test_install_ux.py +87 -2
  40. package/omega/Agentik_Engine/tests/test_paperclip_status.py +142 -0
  41. package/omega/Agentik_Engine/tests/test_prompt_audit.py +281 -0
  42. package/omega/Agentik_Engine/tests/test_tmux_palette.py +94 -0
  43. package/omega/Agentik_Engine/tests/test_tui_runtime.py +156 -0
  44. package/omega/Agentik_SSOT/VERSION +1 -1
  45. package/omega/Agentik_SSOT/docs/AUDIT-V0.19.39.md +161 -0
  46. package/omega/Agentik_SSOT/docs/AUDIT-V0.19.40.md +163 -0
  47. package/omega/Agentik_SSOT/rules/audit-gates.md +189 -0
  48. package/omega/Agentik_SSOT/rules/constitution.md +7 -0
  49. package/omega/Agentik_SSOT/rules/orchestration.md +215 -0
  50. package/omega/Agentik_SSOT/rules/prompt-protocols.md +219 -0
  51. package/omega/Agentik_SSOT/rules/scope-safety.md +197 -0
  52. package/omega/Agentik_SSOT/rules/three-laws.md +214 -0
  53. package/omega/Agentik_SSOT/rules/verified-completion.md +216 -0
  54. package/package.json +1 -1
@@ -325,6 +325,22 @@ def _spawn_with_shell_then_run(
325
325
  return name
326
326
 
327
327
 
328
+ def omega_window_alive(window_name: str) -> bool:
329
+ """True if a window named ``window_name`` exists inside the Omega
330
+ master tmux session.
331
+
332
+ Used by the TUI chat-list panel to render ● (alive) vs ○ (off) next
333
+ to AISB-chat / Hermès-chat. Cheap — one ``tmux list-windows`` call;
334
+ returns False on any error including 'no Omega session'.
335
+ """
336
+ if not is_alive("Omega"):
337
+ return False
338
+ rc, out = _tmux("list-windows", "-t", "Omega", "-F", "#W")
339
+ if rc != 0:
340
+ return False
341
+ return window_name in (out or "").splitlines()
342
+
343
+
328
344
  def spawn_chat_in_omega(
329
345
  window_name: str,
330
346
  *,
@@ -567,7 +583,7 @@ set -s escape-time 10
567
583
  # session (AISB / oracle / worker / dev / linear / home / other) without
568
584
  # leaving tmux.
569
585
  # ════════════════════════════════════════════════════════════════════
570
- bind-key Z display-popup -E -w 80% -h 80% "omega tmux menu"
586
+ bind-key Z display-popup -E -w 100% -h 100% "omega tmux menu"
571
587
 
572
588
  # Prefix+S — native session list (tmux's built-in choose-tree)
573
589
  bind-key S choose-tree -Zs
@@ -575,18 +591,28 @@ bind-key S choose-tree -Zs
575
591
  # Option+/ → session switcher (fzf popup, pick any live tmux session)
576
592
  # Option+z → Omega action menu (spawn Omega if missing + switch-client)
577
593
  # macOS Option key → enable "Use Option as Meta" in your terminal.
578
- bind-key -n M-/ display-popup -E -h 80% -w 90% "omega tmux switcher"
594
+ bind-key -n M-/ display-popup -E -w 100% -h 100% "omega tmux switcher"
579
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"
580
596
 
581
597
  # ════════════════════════════════════════════════════════════════════
582
- # Claude Code LIGHT theme (white-paper)
598
+ # Pixel/CRT amber theme (tmux-claude inspired)
599
+ # xterm colour137 ≈ #af8700 — amber/gold on whatever your terminal bg is.
600
+ # Theme-neutral: inherits terminal background, works in light + dark.
583
601
  # ════════════════════════════════════════════════════════════════════
584
- set -g status-style "bg=#FAFAF7,fg=#3D3929"
585
- set -g status-left "#[fg=#D97757,bold]Ω #S #[fg=#A8A29E] "
586
- set -g status-right "#[fg=#88837A]%H:%M #(omega tmux count 2>/dev/null) sessions"
587
- set -g pane-border-style "fg=#A8A29E"
588
- set -g pane-active-border-style "fg=#D97757"
589
- set -g message-style "bg=#E5E2DD,fg=#3D3929"
602
+ set -g status-style "fg=default"
603
+ set -g status-left ' #[fg=colour137,bold]Ω #S #[fg=default,nobold] #[fg=colour137]#(omega tmux count 2>/dev/null) sess #[fg=default]• #[fg=colour137]%H:%M '
604
+ set -g status-right '#[fg=default]TS #[fg=colour137]#(omega tmux count 2>/dev/null) #[fg=default]• #[fg=colour137]Ω '
605
+
606
+ # Hide the window list (tmux-claude clean look).
607
+ setw -g window-status-format ''
608
+ setw -g window-status-current-format ''
609
+ setw -g window-status-separator ''
610
+
611
+ # Pane borders — amber on active, inherit on inactive.
612
+ set -g pane-border-style "fg=default"
613
+ set -g pane-active-border-style "fg=colour137"
614
+ set -g message-style "fg=colour137,bg=default,bold"
615
+ set -g mode-style "fg=default,bg=colour137"
590
616
  """
591
617
 
592
618
 
@@ -665,7 +691,7 @@ set -g @scroll-without-changing-pane on
665
691
  # Prefix+Z — OmegaOS session picker
666
692
  # Opens a tmux popup with `omega tmux menu`.
667
693
  # ════════════════════════════════════════════════════════════════════
668
- bind-key Z display-popup -E -w 80% -h 80% "omega tmux menu"
694
+ bind-key Z display-popup -E -w 100% -h 100% "omega tmux menu"
669
695
 
670
696
  # Prefix+S — native choose-tree
671
697
  bind-key S choose-tree -Zs
@@ -693,30 +719,39 @@ bind-key r source-file ~/.tmux.conf \\; display-message "tmux.conf reloaded"
693
719
  # ────────────────────────────────────────────────────────────────────
694
720
  # popup -E runs the command in a tmux popup overlay (modal). The popup
695
721
  # closes when the command exits, returning the user to their previous
696
- # pane. -h 80% / -w 90% sizes the popup.
697
- bind-key -n M-/ display-popup -E -h 80% -w 90% "omega tmux switcher"
722
+ # 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"
698
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"
699
725
 
700
726
  # ════════════════════════════════════════════════════════════════════
701
- # Claude Code LIGHT theme (white-paper) — status bar + borders
727
+ # Pixel/CRT amber theme (tmux-claude inspired)
702
728
  # ════════════════════════════════════════════════════════════════════
703
- # Colours derived from tweakcn.com/r/themes/claude.json:
704
- # bg #FAFAF7 (cream) fg #3D3929 (slate) accent #D97757 (orange)
705
- # muted #88837A (warm gray) border #A8A29E (soft)
706
- set -g status-style "bg=#FAFAF7,fg=#3D3929"
707
- set -g status-left "#[fg=#D97757,bold]Ω #S #[fg=#A8A29E]│ "
708
- set -g status-right "#[fg=#88837A]%H:%M #(omega tmux count 2>/dev/null) sessions"
729
+ # xterm colour137 #af8700 — amber/gold. Theme-neutral: status bar
730
+ # inherits the terminal background, so it looks right in any color
731
+ # scheme (dark, light, solarized, etc.).
732
+ #
733
+ # Bullets `•` everywhere as separators, accent colour137 for values,
734
+ # default fg for labels. Window list HIDDEN (tmux-claude clean look)
735
+ # the operator switches via Option+/ or `omega tmux menu` instead.
736
+ set -g status-style "fg=default"
737
+ set -g status-left ' #[fg=colour137,bold]Ω #S #[fg=default,nobold]• #[fg=colour137]#(omega tmux count 2>/dev/null) sess #[fg=default]• #[fg=colour137]%H:%M '
738
+ set -g status-right '#[fg=default]CPU #[fg=colour137]#(awk "{print int(\\$1*100/$(nproc))}" /proc/loadavg 2>/dev/null || echo --)% #[fg=default]• #[fg=colour137]Ω v#(cat $OMEGA_HOME/Agentik_SSOT/VERSION 2>/dev/null || echo dev) '
709
739
  set -g status-interval 5
710
- set -g status-left-length 40
740
+ set -g status-left-length 60
711
741
  set -g status-right-length 60
712
742
 
713
- # Pane borders soft warm gray, accent orange on the active pane.
714
- set -g pane-border-style "fg=#A8A29E"
715
- set -g pane-active-border-style "fg=#D97757"
743
+ # Hide the window list (tmux-claude clean look).
744
+ setw -g window-status-format ''
745
+ setw -g window-status-current-format ''
746
+ setw -g window-status-separator ''
747
+
748
+ # Pane borders — amber on active, inherit on inactive.
749
+ set -g pane-border-style "fg=default"
750
+ set -g pane-active-border-style "fg=colour137"
716
751
 
717
- # Message + copy-mode prompts — same palette.
718
- set -g message-style "bg=#E5E2DD,fg=#3D3929"
719
- set -g mode-style "bg=#D97757,fg=#FAFAF7"
752
+ # Message + copy-mode prompts — amber on terminal bg.
753
+ set -g message-style "fg=colour137,bg=default,bold"
754
+ set -g mode-style "fg=default,bg=colour137"
720
755
  """
721
756
 
722
757
 
@@ -441,11 +441,16 @@ def _arrow_menu() -> int:
441
441
  OMEGA_BIN = str(HOME / "Agentik_Tools" / "bin" / "omega")
442
442
 
443
443
  # ANSI escapes — used via fzf --ansi.
444
- ORANGE = "\033[38;2;217;119;87m"
445
- MUTED = "\033[38;2;136;131;122m"
446
- BOLD = "\033[1m"
447
- DIM = "\033[2m"
448
- RST = "\033[0m"
444
+ # Pixel/CRT amber palette — colour137 family.
445
+ # Inherits terminal background (no cream); accent = amber gold.
446
+ AMBER = "\033[38;5;137m" # xterm 256 colour137 = amber gold
447
+ AMBER_BR = "\033[38;5;215m" # brighter amber for active/highlighted rows
448
+ MUTED = "\033[38;5;240m" # dim gray
449
+ BOLD = "\033[1m"
450
+ DIM = "\033[2m"
451
+ RST = "\033[0m"
452
+ # Backward-compat alias — existing code may reference ORANGE.
453
+ ORANGE = AMBER
449
454
 
450
455
  def _label(name: str, hint: str = "") -> str:
451
456
  """Two-column label: name on the left, hint dimmed on the right."""
@@ -462,46 +467,180 @@ def _arrow_menu() -> int:
462
467
  # `omega_engine.provider_state` module both can read.
463
468
  from omega_engine.provider_state import active_provider as _active_provider
464
469
 
470
+ # v0.19.39 — chat-first redesign. The TUI now opens on CONVERSATIONS
471
+ # (live tmux sessions: AISB chat, Hermès chat, active Oracles per
472
+ # project, active Workers per task) with ●/○ status dots. Setup,
473
+ # config, infra, audits, scrape, etc. move into sub-menus opened from
474
+ # a short "MENU" section. Reasoning: the user spends 99% of their
475
+ # time in conversations — the menu should reflect that, not bury the
476
+ # chats below 6 sections of admin options.
477
+ DOT_ON = f"{AMBER}●{RST}" # alive
478
+ DOT_OFF = f"{MUTED}○{RST}" # not running
479
+
480
+ def _dot(alive: bool) -> str:
481
+ return DOT_ON if alive else DOT_OFF
482
+
483
+ def _conv_label(name: str, hint: str, alive: bool) -> str:
484
+ """Conversation row: `<dot> <name> <dim hint>`."""
485
+ dot = _dot(alive)
486
+ return f" {dot} {name:<32}{DIM}{hint}{RST}"
487
+
488
+ def _paperclip_status_quick() -> tuple[bool, str]:
489
+ """Lightweight inline Paperclip probe — short timeout, no raise.
490
+ Returns (alive, hint). Hint is empty when down, ``localhost:8080``
491
+ (or whatever port the bridge reports) when up. Uses the new
492
+ ``paperclip_bridge.is_running()`` from chantier 4 when present;
493
+ falls back gracefully to a plain TCP probe.
494
+ """
495
+ try:
496
+ from omega_engine.paperclip_bridge import is_running as _pcst
497
+ st = _pcst(HOME)
498
+ if getattr(st, "running", False):
499
+ port = getattr(st, "port", None) or 8080
500
+ return True, f"localhost:{port}"
501
+ except Exception: # noqa: BLE001
502
+ # Fallback: a 200ms TCP probe to 127.0.0.1:8080.
503
+ import socket as _s
504
+ try:
505
+ with _s.create_connection(("127.0.0.1", 8080), timeout=0.2):
506
+ return True, "localhost:8080"
507
+ except OSError:
508
+ pass
509
+ return False, ""
510
+
511
+ def _active_oracles_and_workers() -> tuple[list, list]:
512
+ """Categorize live tmux sessions into (oracles, workers).
513
+ Each entry is the ``TmuxSession`` instance."""
514
+ all_sessions = tmux.list_sessions(HOME)
515
+ oracles = sorted([s for s in all_sessions if s.category == "oracle"],
516
+ key=lambda x: x.name)
517
+ workers = sorted([s for s in all_sessions if s.category == "worker"],
518
+ key=lambda x: x.name)
519
+ return oracles, workers
520
+
465
521
  def _build_items() -> list[tuple[str, str]]:
466
- """Return (display, action_key) — section headers have key '__sep__'."""
522
+ """Return (display, action_key) — section headers have key '__sep__'.
523
+
524
+ v0.19.39 layout (chat-first):
525
+ 1. CONVERSATIONS — AISB / Hermès / live Oracles / live Workers
526
+ 2. QUICK ACTIONS — new chat, new project, run mission, Paperclip
527
+ 3. MENU — sub-menus (audits, setup, infra, health)
528
+ 4. EXIT
529
+ """
467
530
  provider = _active_provider()
468
- sessions = tmux.list_sessions(HOME)
469
- return [
470
- (_section("CHAT"), "__sep__"),
471
- (_label("AISB master chat", "→ claude (Max OAuth)"), "open:aisb"),
472
- (_label("Hermès companion", "→ claude (Anthropic API)"), "open:hermes"),
473
- (_label("Switch LLM", f"current: {provider}"), "switch:provider"),
474
- ("", "__sep__"),
475
- (_section("PROJECTS"), "__sep__"),
476
- (_label("New project", "Genesis pipeline"), "genesis:new"),
477
- (_label("Open project shell"), "project:open"),
478
- ("", "__sep__"),
479
- (_section("AUDITS & MISSIONS"), "__sep__"),
480
- (_label("Run a mission", "verified completion"), "run:mission"),
481
- (_label("Quality Arsenal", "17 forensic audits"), "audit:menu"),
482
- ("", "__sep__"),
483
- (_section("INFRASTRUCTURE"), "__sep__"),
484
- (_label("Sessions", f"{len(sessions)} active"), "sessions:list"),
485
- (_label("Accounts", "Claude Max pool"), "accounts:menu"),
486
- (_label("Vault", "encrypted secrets"), "vault:menu"),
487
- ("", "__sep__"),
488
- (_section("HEALTH"), "__sep__"),
489
- (_label("omega doctor", "full health check"), "cmd:doctor"),
490
- (_label("omega status", "task state"), "cmd:status"),
491
- ("", "__sep__"),
492
- (_section("SCRAPE"), "__sep__"),
493
- (_label("Stealth scrape", "CloakBrowser"), "scrape:cloak"),
494
- (_label("Fast scrape", "Scrapling"), "scrape:scrapling"),
495
- ("", "__sep__"),
496
- (_section("GOVERNANCE (Paperclip L0)"), "__sep__"),
497
- (_label("Paperclip dashboard","web UI for 14 agents"), "paperclip:dashboard"),
498
- (_label("Paperclip status", "bridge health"), "paperclip:status"),
499
- (_label("Paperclip register","(re)sync OmegaOS company"), "paperclip:register"),
500
- ("", "__sep__"),
501
- (_section("EXIT"), "__sep__"),
502
- (_label("Detach", "session keeps running"), "detach"),
503
- (_label("Quit Omega", "kills the tmux session"), "quit:kill"),
504
- ]
531
+ aisb_alive = tmux.omega_window_alive("aisb")
532
+ hermes_alive = tmux.omega_window_alive("hermes")
533
+ oracles, workers = _active_oracles_and_workers()
534
+ paperclip_alive, paperclip_hint = _paperclip_status_quick()
535
+
536
+ items: list[tuple[str, str]] = []
537
+
538
+ # ── CONVERSATIONS ───────────────────────────────────────────────
539
+ items.append((_section("CONVERSATIONS"), "__sep__"))
540
+ items.append((_conv_label("AISB master", "claude (Max OAuth)", aisb_alive), "open:aisb"))
541
+ items.append((_conv_label("Hermès", "claude (Anthropic API)", hermes_alive), "open:hermes"))
542
+
543
+ # Per-project live oracles + workers (only when there are any —
544
+ # don't waste a row when the user has nothing running).
545
+ if oracles or workers:
546
+ items.append(("", "__sep__"))
547
+ if oracles:
548
+ items.append((f" {DIM}— Active Oracles ({len(oracles)}) —{RST}", "__sep__"))
549
+ for s in oracles:
550
+ proj = s.project or "?"
551
+ hint = f"project: {proj}"
552
+ items.append((_conv_label(s.name, hint, True), f"attach:{s.name}"))
553
+ if workers:
554
+ items.append((f" {DIM}— Active Workers ({len(workers)}) —{RST}", "__sep__"))
555
+ for s in workers:
556
+ # Extract task from `<proj>-worker-N-<task>`.
557
+ task = s.name.split("worker-", 1)[-1].split("-", 1)[-1] if "worker-" in s.name else "?"
558
+ hint = f"task: {task}"
559
+ items.append((_conv_label(s.name, hint, True), f"attach:{s.name}"))
560
+
561
+ items.append(("", "__sep__"))
562
+
563
+ # ── QUICK ACTIONS ───────────────────────────────────────────────
564
+ items.append((_section("QUICK ACTIONS"), "__sep__"))
565
+ items.append((_label("+ New AISB chat", "fresh session"), "open:aisb:new"))
566
+ items.append((_label("+ New Hermès chat", "fresh session"), "open:hermes:new"))
567
+ items.append((_label("+ New project", "Genesis pipeline"), "genesis:new"))
568
+ items.append((_label("Run a mission", "verified completion"), "run:mission"))
569
+ # Paperclip dashboard with live status indicator.
570
+ pclbl = f"Paperclip dashboard"
571
+ pcdot = _dot(paperclip_alive)
572
+ pchint = paperclip_hint if paperclip_alive else "not running"
573
+ items.append((f" {pcdot} {pclbl:<32}{DIM}{pchint}{RST}", "paperclip:dashboard"))
574
+
575
+ items.append(("", "__sep__"))
576
+
577
+ # ── MENU (sub-menus for everything else) ────────────────────────
578
+ items.append((_section("MENU"), "__sep__"))
579
+ items.append((_label("Quality Arsenal", "17 forensic audits"), "submenu:audits"))
580
+ items.append((_label("Setup & config", f"LLM: {provider}"), "submenu:setup"))
581
+ items.append((_label("Infrastructure", "sessions, scrape"), "submenu:infra"))
582
+ items.append((_label("Health checks", "doctor, status"), "submenu:health"))
583
+ items.append((_label("Paperclip governance", "register, status"), "submenu:paperclip"))
584
+
585
+ items.append(("", "__sep__"))
586
+
587
+ # ── EXIT ────────────────────────────────────────────────────────
588
+ items.append((_section("EXIT"), "__sep__"))
589
+ items.append((_label("Detach", "session keeps running"), "detach"))
590
+ items.append((_label("Quit Omega", "kills the tmux session"), "quit:kill"))
591
+
592
+ return items
593
+
594
+ def _submenu_items(name: str) -> list[tuple[str, str]]:
595
+ """Return (display, action_key) for one of the named sub-menus.
596
+ Every sub-menu ends with a synthetic '← back' row that aborts the
597
+ cascaded fzf with key 'back'."""
598
+ if name == "audits":
599
+ return [
600
+ (_section("QUALITY ARSENAL"), "__sep__"),
601
+ (_label("List all 17 audits"), "audit:menu"),
602
+ (_label("Run an audit", "interactive"), "audit:run"),
603
+ ("", "__sep__"),
604
+ (_label("← back"), "back"),
605
+ ]
606
+ if name == "setup":
607
+ return [
608
+ (_section("SETUP & CONFIG"), "__sep__"),
609
+ (_label("Switch LLM provider"), "switch:provider"),
610
+ (_label("Accounts", "Claude Max pool"), "accounts:menu"),
611
+ (_label("Vault", "encrypted secrets"), "vault:menu"),
612
+ ("", "__sep__"),
613
+ (_label("← back"), "back"),
614
+ ]
615
+ if name == "infra":
616
+ return [
617
+ (_section("INFRASTRUCTURE"), "__sep__"),
618
+ (_label("List tmux sessions"), "sessions:list"),
619
+ (_label("Open project shell"), "project:open"),
620
+ ("", "__sep__"),
621
+ (_label("Stealth scrape", "CloakBrowser"), "scrape:cloak"),
622
+ (_label("Fast scrape", "Scrapling"), "scrape:scrapling"),
623
+ ("", "__sep__"),
624
+ (_label("← back"), "back"),
625
+ ]
626
+ if name == "health":
627
+ return [
628
+ (_section("HEALTH"), "__sep__"),
629
+ (_label("omega doctor", "full health check"), "cmd:doctor"),
630
+ (_label("omega status", "task state"), "cmd:status"),
631
+ ("", "__sep__"),
632
+ (_label("← back"), "back"),
633
+ ]
634
+ if name == "paperclip":
635
+ return [
636
+ (_section("PAPERCLIP GOVERNANCE (L0)"), "__sep__"),
637
+ (_label("Open dashboard"), "paperclip:dashboard"),
638
+ (_label("Status", "bridge health"), "paperclip:status"),
639
+ (_label("Register", "(re)sync OmegaOS company"), "paperclip:register"),
640
+ ("", "__sep__"),
641
+ (_label("← back"), "back"),
642
+ ]
643
+ return [(_label("← back"), "back")]
505
644
 
506
645
  def _run_inline(cmd_argv, *, shell: bool = False) -> None:
507
646
  os.system("clear")
@@ -525,23 +664,29 @@ def _arrow_menu() -> int:
525
664
  except (EOFError, KeyboardInterrupt):
526
665
  return ""
527
666
 
528
- while True:
529
- items = _build_items()
530
- # v0.19.37 bulletproof matching via tab-delimited index column.
531
- # fzf with --ansi STRIPS escape codes from stdout, which broke
532
- # the old `display == pick` exact-string match (every pick fell
533
- # through, menu just reloaded). Now we prefix each line with a
534
- # stable index (`<i>\t<display>`) and use --with-nth=2.. so fzf
535
- # only DISPLAYS the second column but RETURNS the full line.
536
- # We split off the index and look up the action — survives any
537
- # ANSI/whitespace transformation fzf might apply.
667
+ def _pick(items: list[tuple[str, str]],
668
+ header_extra: str = "") -> str | None:
669
+ """Render one fzf pass over ``items`` and return the chosen
670
+ action key (the second element of the picked tuple), or ``None``
671
+ on Esc / Ctrl-C / error.
672
+
673
+ Skips section headers and blank rows automatically — the caller
674
+ never has to worry about ``__sep__`` picks.
675
+
676
+ v0.19.37 lock-in: input is tab-delimited ``<idx>\\t<display>``,
677
+ fzf --with-nth=2.. shows only the visual column, and we parse
678
+ the index column out of stdout — survives any ANSI/whitespace
679
+ transformation fzf applies.
680
+ """
538
681
  lines = []
539
- for i, (disp, key) in enumerate(items):
682
+ for i, (disp, _key) in enumerate(items):
540
683
  lines.append(f"{i}\t{disp}")
541
684
  header = (
542
685
  f"{ORANGE}{BOLD}Ω Omega OS v{__version__}{RST} "
543
686
  f"{MUTED}• ↑↓ navigate • ↵ pick • / search • Esc refresh{RST}"
544
687
  )
688
+ if header_extra:
689
+ header = f"{header}\n{MUTED}{header_extra}{RST}"
545
690
  try:
546
691
  proc = subprocess.run(
547
692
  ["fzf",
@@ -560,44 +705,67 @@ def _arrow_menu() -> int:
560
705
  "--pointer=▶",
561
706
  "--marker=●",
562
707
  "--color="
563
- "bg:#FAFAF7,"
564
- "fg:#3D3929,"
565
- "bg+:#E5E2DD,"
566
- "fg+:#D97757,"
567
- "hl:#D97757,"
568
- "hl+:#D97757,"
569
- "prompt:#D97757,"
570
- "pointer:#D97757,"
571
- "marker:#D97757,"
572
- "header:#88837A,"
573
- "border:#A8A29E,"
574
- "info:#88837A,"
575
- "spinner:#D97757,"
576
- "gutter:#FAFAF7"],
708
+ "bg:-1,"
709
+ "fg:default,"
710
+ "bg+:#1c1c1c,"
711
+ "fg+:colour215,"
712
+ "hl:colour137,"
713
+ "hl+:colour215,"
714
+ "prompt:colour137,"
715
+ "pointer:colour137,"
716
+ "marker:colour215,"
717
+ "header:colour137,"
718
+ "border:colour137,"
719
+ "info:colour137,"
720
+ "spinner:colour137,"
721
+ "gutter:-1"],
577
722
  input="\n".join(lines), capture_output=True, text=True,
578
723
  )
579
724
  except (KeyboardInterrupt, subprocess.SubprocessError):
580
- return 0
725
+ return None
581
726
  if proc.returncode != 0:
582
- # Esc → refresh.
583
- continue
727
+ return None
584
728
  pick = proc.stdout.rstrip("\n")
585
- # Parse the index column. The tab is always the first \t in the
586
- # picked line (the rest of the display may contain colour codes
587
- # but no tabs).
588
729
  if "\t" not in pick:
589
- continue
590
- idx_str, _disp_part = pick.split("\t", 1)
730
+ return None
731
+ idx_str, _ = pick.split("\t", 1)
591
732
  try:
592
733
  idx = int(idx_str)
593
734
  except ValueError:
594
- continue
735
+ return None
595
736
  if not (0 <= idx < len(items)):
596
- continue
737
+ return None
597
738
  action = items[idx][1]
598
739
  if action == "__sep__":
599
- # User picked a section header — silent reroll.
740
+ return None
741
+ return action
742
+
743
+ def _open_submenu(name: str) -> str | None:
744
+ """Render a sub-menu in a loop until the user picks an action OR
745
+ chooses 'back'. Returns the action key to dispatch (the caller
746
+ runs it), or None when the user backs out / Esc'd.
747
+ """
748
+ while True:
749
+ sub_items = _submenu_items(name)
750
+ picked = _pick(sub_items, header_extra=f"sub-menu: {name} · Esc/← back to main")
751
+ if picked is None or picked == "back":
752
+ return None
753
+ return picked
754
+
755
+ while True:
756
+ items = _build_items()
757
+ action = _pick(items)
758
+ if action is None:
759
+ # Esc → just refresh the conversations panel (cheap — re-renders
760
+ # with live tmux state).
600
761
  continue
762
+ # Sub-menu indirection: when the user picks a sub-menu, render it
763
+ # in its own loop until they pick a real action or back out.
764
+ if action.startswith("submenu:"):
765
+ sub_name = action.split(":", 1)[1]
766
+ action = _open_submenu(sub_name)
767
+ if action is None:
768
+ continue
601
769
 
602
770
  # === Dispatch ===
603
771
  if action == "detach":
@@ -606,19 +774,50 @@ def _arrow_menu() -> int:
606
774
  if action == "quit:kill":
607
775
  subprocess.run(["tmux", "kill-session", "-t", "Omega"])
608
776
  return 0
609
- if action == "open:aisb":
610
- # v0.19.31 open as a WINDOW in Omega, not a separate session.
611
- # User stays in Omega; Ctrl-b 0 returns to menu, Ctrl-b d
612
- # detaches the whole Omega (and `omega` brings them back).
777
+ # v0.19.39 attach to a live Oracle / Worker tmux session. The
778
+ # menu lists them as `attach:<session_name>`. We use
779
+ # `select-window` when the session is already a window inside
780
+ # Omega; otherwise we switch the client to that session.
781
+ if action.startswith("attach:"):
782
+ target = action.split(":", 1)[1]
783
+ # If the target is a window of Omega, just select it. Else
784
+ # switch-client to that session (works under nested tmux).
785
+ wnames = subprocess.run(
786
+ ["tmux", "list-windows", "-t", "Omega", "-F", "#W"],
787
+ capture_output=True, text=True,
788
+ )
789
+ if (wnames.returncode == 0
790
+ and target in (wnames.stdout or "").splitlines()):
791
+ subprocess.run(["tmux", "select-window",
792
+ "-t", f"Omega:{target}"])
793
+ else:
794
+ # Switch to the foreign session. Use switch-client (works
795
+ # when we're already inside a tmux client) or fall back to
796
+ # attach for non-tmux contexts.
797
+ rc = subprocess.run(
798
+ ["tmux", "switch-client", "-t", target],
799
+ capture_output=True,
800
+ ).returncode
801
+ if rc != 0:
802
+ _run_inline(f"echo ' cannot attach to {target} — is it alive?' "
803
+ f"&& tmux has-session -t {target} && "
804
+ f"echo ' tmux says: yes' || echo ' tmux says: no'",
805
+ shell=True)
806
+ continue
807
+ # v0.19.39 — "+ New" actions: kill existing chat window first so
808
+ # the user gets a clean fresh session.
809
+ if action in ("open:aisb", "open:aisb:new"):
810
+ force = (action == "open:aisb:new")
613
811
  persona = HOME / "Agentik_SSOT" / "agents" / "aisb" / "CLAUDE.md"
614
812
  ctx_dir = tmux._ensure_chat_context_dir(HOME, "aisb-master", persona)
615
813
  tmux.spawn_chat_in_omega(
616
814
  "aisb", ctx_dir=ctx_dir, run_command="claude",
617
- force_replace=False,
815
+ force_replace=force,
618
816
  )
619
817
  subprocess.run(["tmux", "select-window", "-t", "Omega:aisb"])
620
818
  continue
621
- if action == "open:hermes":
819
+ if action in ("open:hermes", "open:hermes:new"):
820
+ force = (action == "open:hermes:new")
622
821
  persona = HOME / "Agentik_SSOT" / "docs" / "LAYERS.md"
623
822
  ctx_dir = tmux._ensure_chat_context_dir(HOME, "hermes", persona)
624
823
  try:
@@ -632,10 +831,18 @@ def _arrow_menu() -> int:
632
831
  if api_key else "claude")
633
832
  tmux.spawn_chat_in_omega(
634
833
  "hermes", ctx_dir=ctx_dir, run_command=run_cmd,
635
- force_replace=False,
834
+ force_replace=force,
636
835
  )
637
836
  subprocess.run(["tmux", "select-window", "-t", "Omega:hermes"])
638
837
  continue
838
+ if action == "audit:run":
839
+ audit_id = _prompt("audit id (codeaudit, uiuxaudit, flowaudit, ...)")
840
+ if audit_id:
841
+ _run_inline([OMEGA_BIN, "audit", audit_id])
842
+ continue
843
+ # (v0.19.31 open:aisb / open:hermes legacy handlers removed in
844
+ # v0.19.39 — the new unified handlers above (which accept both
845
+ # plain and `:new` variants) cover the same surface.)
639
846
  if action == "switch:provider":
640
847
  _run_inline([OMEGA_BIN, "switch"]); continue
641
848
  if action == "genesis:new":
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omega-engine"
3
- version = "0.19.38"
3
+ version = "0.19.40"
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"