@agentikos/omega-os 0.19.37 → 0.19.39
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.
- package/bin/omega-os.js +6 -1
- package/bootstrap/lib/steps.sh +43 -0
- package/install.sh +5 -0
- package/omega/Agentik_Engine/omega_engine/__init__.py +1 -1
- package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/paperclip_bridge.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/prompt_audit.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/tmux.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/tui.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/cli.py +73 -0
- package/omega/Agentik_Engine/omega_engine/paperclip_bridge.py +110 -0
- package/omega/Agentik_Engine/omega_engine/prompt_audit.py +395 -0
- package/omega/Agentik_Engine/omega_engine/tmux.py +16 -0
- package/omega/Agentik_Engine/omega_engine/tui.py +269 -67
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313-pytest-8.4.2.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_installer_wiring.py +130 -0
- package/omega/Agentik_Engine/tests/test_paperclip_status.py +142 -0
- package/omega/Agentik_Engine/tests/test_prompt_audit.py +199 -0
- package/omega/Agentik_Engine/tests/test_tui_runtime.py +106 -0
- package/omega/Agentik_SSOT/VERSION +1 -1
- package/omega/Agentik_SSOT/docs/AUDIT-V0.19.38.md +90 -0
- package/omega/Agentik_SSOT/docs/AUDIT-V0.19.39.md +161 -0
- package/omega/Agentik_SSOT/rules/audit-gates.md +189 -0
- package/omega/Agentik_SSOT/rules/constitution.md +7 -0
- package/omega/Agentik_SSOT/rules/orchestration.md +215 -0
- package/omega/Agentik_SSOT/rules/prompt-protocols.md +219 -0
- package/omega/Agentik_SSOT/rules/scope-safety.md +197 -0
- package/omega/Agentik_SSOT/rules/three-laws.md +214 -0
- package/omega/Agentik_SSOT/rules/verified-completion.md +216 -0
- package/package.json +1 -1
|
@@ -462,46 +462,180 @@ def _arrow_menu() -> int:
|
|
|
462
462
|
# `omega_engine.provider_state` module both can read.
|
|
463
463
|
from omega_engine.provider_state import active_provider as _active_provider
|
|
464
464
|
|
|
465
|
+
# v0.19.39 — chat-first redesign. The TUI now opens on CONVERSATIONS
|
|
466
|
+
# (live tmux sessions: AISB chat, Hermès chat, active Oracles per
|
|
467
|
+
# project, active Workers per task) with ●/○ status dots. Setup,
|
|
468
|
+
# config, infra, audits, scrape, etc. move into sub-menus opened from
|
|
469
|
+
# a short "MENU" section. Reasoning: the user spends 99% of their
|
|
470
|
+
# time in conversations — the menu should reflect that, not bury the
|
|
471
|
+
# chats below 6 sections of admin options.
|
|
472
|
+
DOT_ON = f"{ORANGE}●{RST}" # alive
|
|
473
|
+
DOT_OFF = f"{MUTED}○{RST}" # not running
|
|
474
|
+
|
|
475
|
+
def _dot(alive: bool) -> str:
|
|
476
|
+
return DOT_ON if alive else DOT_OFF
|
|
477
|
+
|
|
478
|
+
def _conv_label(name: str, hint: str, alive: bool) -> str:
|
|
479
|
+
"""Conversation row: `<dot> <name> <dim hint>`."""
|
|
480
|
+
dot = _dot(alive)
|
|
481
|
+
return f" {dot} {name:<32}{DIM}{hint}{RST}"
|
|
482
|
+
|
|
483
|
+
def _paperclip_status_quick() -> tuple[bool, str]:
|
|
484
|
+
"""Lightweight inline Paperclip probe — short timeout, no raise.
|
|
485
|
+
Returns (alive, hint). Hint is empty when down, ``localhost:8080``
|
|
486
|
+
(or whatever port the bridge reports) when up. Uses the new
|
|
487
|
+
``paperclip_bridge.is_running()`` from chantier 4 when present;
|
|
488
|
+
falls back gracefully to a plain TCP probe.
|
|
489
|
+
"""
|
|
490
|
+
try:
|
|
491
|
+
from omega_engine.paperclip_bridge import is_running as _pcst
|
|
492
|
+
st = _pcst(HOME)
|
|
493
|
+
if getattr(st, "running", False):
|
|
494
|
+
port = getattr(st, "port", None) or 8080
|
|
495
|
+
return True, f"localhost:{port}"
|
|
496
|
+
except Exception: # noqa: BLE001
|
|
497
|
+
# Fallback: a 200ms TCP probe to 127.0.0.1:8080.
|
|
498
|
+
import socket as _s
|
|
499
|
+
try:
|
|
500
|
+
with _s.create_connection(("127.0.0.1", 8080), timeout=0.2):
|
|
501
|
+
return True, "localhost:8080"
|
|
502
|
+
except OSError:
|
|
503
|
+
pass
|
|
504
|
+
return False, ""
|
|
505
|
+
|
|
506
|
+
def _active_oracles_and_workers() -> tuple[list, list]:
|
|
507
|
+
"""Categorize live tmux sessions into (oracles, workers).
|
|
508
|
+
Each entry is the ``TmuxSession`` instance."""
|
|
509
|
+
all_sessions = tmux.list_sessions(HOME)
|
|
510
|
+
oracles = sorted([s for s in all_sessions if s.category == "oracle"],
|
|
511
|
+
key=lambda x: x.name)
|
|
512
|
+
workers = sorted([s for s in all_sessions if s.category == "worker"],
|
|
513
|
+
key=lambda x: x.name)
|
|
514
|
+
return oracles, workers
|
|
515
|
+
|
|
465
516
|
def _build_items() -> list[tuple[str, str]]:
|
|
466
|
-
"""Return (display, action_key) — section headers have key '__sep__'.
|
|
517
|
+
"""Return (display, action_key) — section headers have key '__sep__'.
|
|
518
|
+
|
|
519
|
+
v0.19.39 layout (chat-first):
|
|
520
|
+
1. CONVERSATIONS — AISB / Hermès / live Oracles / live Workers
|
|
521
|
+
2. QUICK ACTIONS — new chat, new project, run mission, Paperclip
|
|
522
|
+
3. MENU — sub-menus (audits, setup, infra, health)
|
|
523
|
+
4. EXIT
|
|
524
|
+
"""
|
|
467
525
|
provider = _active_provider()
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
(
|
|
484
|
-
|
|
485
|
-
(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
("",
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
526
|
+
aisb_alive = tmux.omega_window_alive("aisb")
|
|
527
|
+
hermes_alive = tmux.omega_window_alive("hermes")
|
|
528
|
+
oracles, workers = _active_oracles_and_workers()
|
|
529
|
+
paperclip_alive, paperclip_hint = _paperclip_status_quick()
|
|
530
|
+
|
|
531
|
+
items: list[tuple[str, str]] = []
|
|
532
|
+
|
|
533
|
+
# ── CONVERSATIONS ───────────────────────────────────────────────
|
|
534
|
+
items.append((_section("CONVERSATIONS"), "__sep__"))
|
|
535
|
+
items.append((_conv_label("AISB master", "claude (Max OAuth)", aisb_alive), "open:aisb"))
|
|
536
|
+
items.append((_conv_label("Hermès", "claude (Anthropic API)", hermes_alive), "open:hermes"))
|
|
537
|
+
|
|
538
|
+
# Per-project live oracles + workers (only when there are any —
|
|
539
|
+
# don't waste a row when the user has nothing running).
|
|
540
|
+
if oracles or workers:
|
|
541
|
+
items.append(("", "__sep__"))
|
|
542
|
+
if oracles:
|
|
543
|
+
items.append((f" {DIM}— Active Oracles ({len(oracles)}) —{RST}", "__sep__"))
|
|
544
|
+
for s in oracles:
|
|
545
|
+
proj = s.project or "?"
|
|
546
|
+
hint = f"project: {proj}"
|
|
547
|
+
items.append((_conv_label(s.name, hint, True), f"attach:{s.name}"))
|
|
548
|
+
if workers:
|
|
549
|
+
items.append((f" {DIM}— Active Workers ({len(workers)}) —{RST}", "__sep__"))
|
|
550
|
+
for s in workers:
|
|
551
|
+
# Extract task from `<proj>-worker-N-<task>`.
|
|
552
|
+
task = s.name.split("worker-", 1)[-1].split("-", 1)[-1] if "worker-" in s.name else "?"
|
|
553
|
+
hint = f"task: {task}"
|
|
554
|
+
items.append((_conv_label(s.name, hint, True), f"attach:{s.name}"))
|
|
555
|
+
|
|
556
|
+
items.append(("", "__sep__"))
|
|
557
|
+
|
|
558
|
+
# ── QUICK ACTIONS ───────────────────────────────────────────────
|
|
559
|
+
items.append((_section("QUICK ACTIONS"), "__sep__"))
|
|
560
|
+
items.append((_label("+ New AISB chat", "fresh session"), "open:aisb:new"))
|
|
561
|
+
items.append((_label("+ New Hermès chat", "fresh session"), "open:hermes:new"))
|
|
562
|
+
items.append((_label("+ New project", "Genesis pipeline"), "genesis:new"))
|
|
563
|
+
items.append((_label("Run a mission", "verified completion"), "run:mission"))
|
|
564
|
+
# Paperclip dashboard with live status indicator.
|
|
565
|
+
pclbl = f"Paperclip dashboard"
|
|
566
|
+
pcdot = _dot(paperclip_alive)
|
|
567
|
+
pchint = paperclip_hint if paperclip_alive else "not running"
|
|
568
|
+
items.append((f" {pcdot} {pclbl:<32}{DIM}{pchint}{RST}", "paperclip:dashboard"))
|
|
569
|
+
|
|
570
|
+
items.append(("", "__sep__"))
|
|
571
|
+
|
|
572
|
+
# ── MENU (sub-menus for everything else) ────────────────────────
|
|
573
|
+
items.append((_section("MENU"), "__sep__"))
|
|
574
|
+
items.append((_label("Quality Arsenal", "17 forensic audits"), "submenu:audits"))
|
|
575
|
+
items.append((_label("Setup & config", f"LLM: {provider}"), "submenu:setup"))
|
|
576
|
+
items.append((_label("Infrastructure", "sessions, scrape"), "submenu:infra"))
|
|
577
|
+
items.append((_label("Health checks", "doctor, status"), "submenu:health"))
|
|
578
|
+
items.append((_label("Paperclip governance", "register, status"), "submenu:paperclip"))
|
|
579
|
+
|
|
580
|
+
items.append(("", "__sep__"))
|
|
581
|
+
|
|
582
|
+
# ── EXIT ────────────────────────────────────────────────────────
|
|
583
|
+
items.append((_section("EXIT"), "__sep__"))
|
|
584
|
+
items.append((_label("Detach", "session keeps running"), "detach"))
|
|
585
|
+
items.append((_label("Quit Omega", "kills the tmux session"), "quit:kill"))
|
|
586
|
+
|
|
587
|
+
return items
|
|
588
|
+
|
|
589
|
+
def _submenu_items(name: str) -> list[tuple[str, str]]:
|
|
590
|
+
"""Return (display, action_key) for one of the named sub-menus.
|
|
591
|
+
Every sub-menu ends with a synthetic '← back' row that aborts the
|
|
592
|
+
cascaded fzf with key 'back'."""
|
|
593
|
+
if name == "audits":
|
|
594
|
+
return [
|
|
595
|
+
(_section("QUALITY ARSENAL"), "__sep__"),
|
|
596
|
+
(_label("List all 17 audits"), "audit:menu"),
|
|
597
|
+
(_label("Run an audit", "interactive"), "audit:run"),
|
|
598
|
+
("", "__sep__"),
|
|
599
|
+
(_label("← back"), "back"),
|
|
600
|
+
]
|
|
601
|
+
if name == "setup":
|
|
602
|
+
return [
|
|
603
|
+
(_section("SETUP & CONFIG"), "__sep__"),
|
|
604
|
+
(_label("Switch LLM provider"), "switch:provider"),
|
|
605
|
+
(_label("Accounts", "Claude Max pool"), "accounts:menu"),
|
|
606
|
+
(_label("Vault", "encrypted secrets"), "vault:menu"),
|
|
607
|
+
("", "__sep__"),
|
|
608
|
+
(_label("← back"), "back"),
|
|
609
|
+
]
|
|
610
|
+
if name == "infra":
|
|
611
|
+
return [
|
|
612
|
+
(_section("INFRASTRUCTURE"), "__sep__"),
|
|
613
|
+
(_label("List tmux sessions"), "sessions:list"),
|
|
614
|
+
(_label("Open project shell"), "project:open"),
|
|
615
|
+
("", "__sep__"),
|
|
616
|
+
(_label("Stealth scrape", "CloakBrowser"), "scrape:cloak"),
|
|
617
|
+
(_label("Fast scrape", "Scrapling"), "scrape:scrapling"),
|
|
618
|
+
("", "__sep__"),
|
|
619
|
+
(_label("← back"), "back"),
|
|
620
|
+
]
|
|
621
|
+
if name == "health":
|
|
622
|
+
return [
|
|
623
|
+
(_section("HEALTH"), "__sep__"),
|
|
624
|
+
(_label("omega doctor", "full health check"), "cmd:doctor"),
|
|
625
|
+
(_label("omega status", "task state"), "cmd:status"),
|
|
626
|
+
("", "__sep__"),
|
|
627
|
+
(_label("← back"), "back"),
|
|
628
|
+
]
|
|
629
|
+
if name == "paperclip":
|
|
630
|
+
return [
|
|
631
|
+
(_section("PAPERCLIP GOVERNANCE (L0)"), "__sep__"),
|
|
632
|
+
(_label("Open dashboard"), "paperclip:dashboard"),
|
|
633
|
+
(_label("Status", "bridge health"), "paperclip:status"),
|
|
634
|
+
(_label("Register", "(re)sync OmegaOS company"), "paperclip:register"),
|
|
635
|
+
("", "__sep__"),
|
|
636
|
+
(_label("← back"), "back"),
|
|
637
|
+
]
|
|
638
|
+
return [(_label("← back"), "back")]
|
|
505
639
|
|
|
506
640
|
def _run_inline(cmd_argv, *, shell: bool = False) -> None:
|
|
507
641
|
os.system("clear")
|
|
@@ -525,23 +659,29 @@ def _arrow_menu() -> int:
|
|
|
525
659
|
except (EOFError, KeyboardInterrupt):
|
|
526
660
|
return ""
|
|
527
661
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
662
|
+
def _pick(items: list[tuple[str, str]],
|
|
663
|
+
header_extra: str = "") -> str | None:
|
|
664
|
+
"""Render one fzf pass over ``items`` and return the chosen
|
|
665
|
+
action key (the second element of the picked tuple), or ``None``
|
|
666
|
+
on Esc / Ctrl-C / error.
|
|
667
|
+
|
|
668
|
+
Skips section headers and blank rows automatically — the caller
|
|
669
|
+
never has to worry about ``__sep__`` picks.
|
|
670
|
+
|
|
671
|
+
v0.19.37 lock-in: input is tab-delimited ``<idx>\\t<display>``,
|
|
672
|
+
fzf --with-nth=2.. shows only the visual column, and we parse
|
|
673
|
+
the index column out of stdout — survives any ANSI/whitespace
|
|
674
|
+
transformation fzf applies.
|
|
675
|
+
"""
|
|
538
676
|
lines = []
|
|
539
|
-
for i, (disp,
|
|
677
|
+
for i, (disp, _key) in enumerate(items):
|
|
540
678
|
lines.append(f"{i}\t{disp}")
|
|
541
679
|
header = (
|
|
542
680
|
f"{ORANGE}{BOLD}Ω Omega OS v{__version__}{RST} "
|
|
543
681
|
f"{MUTED}• ↑↓ navigate • ↵ pick • / search • Esc refresh{RST}"
|
|
544
682
|
)
|
|
683
|
+
if header_extra:
|
|
684
|
+
header = f"{header}\n{MUTED}{header_extra}{RST}"
|
|
545
685
|
try:
|
|
546
686
|
proc = subprocess.run(
|
|
547
687
|
["fzf",
|
|
@@ -577,27 +717,50 @@ def _arrow_menu() -> int:
|
|
|
577
717
|
input="\n".join(lines), capture_output=True, text=True,
|
|
578
718
|
)
|
|
579
719
|
except (KeyboardInterrupt, subprocess.SubprocessError):
|
|
580
|
-
return
|
|
720
|
+
return None
|
|
581
721
|
if proc.returncode != 0:
|
|
582
|
-
|
|
583
|
-
continue
|
|
722
|
+
return None
|
|
584
723
|
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
724
|
if "\t" not in pick:
|
|
589
|
-
|
|
590
|
-
idx_str,
|
|
725
|
+
return None
|
|
726
|
+
idx_str, _ = pick.split("\t", 1)
|
|
591
727
|
try:
|
|
592
728
|
idx = int(idx_str)
|
|
593
729
|
except ValueError:
|
|
594
|
-
|
|
730
|
+
return None
|
|
595
731
|
if not (0 <= idx < len(items)):
|
|
596
|
-
|
|
732
|
+
return None
|
|
597
733
|
action = items[idx][1]
|
|
598
734
|
if action == "__sep__":
|
|
599
|
-
|
|
735
|
+
return None
|
|
736
|
+
return action
|
|
737
|
+
|
|
738
|
+
def _open_submenu(name: str) -> str | None:
|
|
739
|
+
"""Render a sub-menu in a loop until the user picks an action OR
|
|
740
|
+
chooses 'back'. Returns the action key to dispatch (the caller
|
|
741
|
+
runs it), or None when the user backs out / Esc'd.
|
|
742
|
+
"""
|
|
743
|
+
while True:
|
|
744
|
+
sub_items = _submenu_items(name)
|
|
745
|
+
picked = _pick(sub_items, header_extra=f"sub-menu: {name} · Esc/← back to main")
|
|
746
|
+
if picked is None or picked == "back":
|
|
747
|
+
return None
|
|
748
|
+
return picked
|
|
749
|
+
|
|
750
|
+
while True:
|
|
751
|
+
items = _build_items()
|
|
752
|
+
action = _pick(items)
|
|
753
|
+
if action is None:
|
|
754
|
+
# Esc → just refresh the conversations panel (cheap — re-renders
|
|
755
|
+
# with live tmux state).
|
|
600
756
|
continue
|
|
757
|
+
# Sub-menu indirection: when the user picks a sub-menu, render it
|
|
758
|
+
# in its own loop until they pick a real action or back out.
|
|
759
|
+
if action.startswith("submenu:"):
|
|
760
|
+
sub_name = action.split(":", 1)[1]
|
|
761
|
+
action = _open_submenu(sub_name)
|
|
762
|
+
if action is None:
|
|
763
|
+
continue
|
|
601
764
|
|
|
602
765
|
# === Dispatch ===
|
|
603
766
|
if action == "detach":
|
|
@@ -606,19 +769,50 @@ def _arrow_menu() -> int:
|
|
|
606
769
|
if action == "quit:kill":
|
|
607
770
|
subprocess.run(["tmux", "kill-session", "-t", "Omega"])
|
|
608
771
|
return 0
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
772
|
+
# v0.19.39 — attach to a live Oracle / Worker tmux session. The
|
|
773
|
+
# menu lists them as `attach:<session_name>`. We use
|
|
774
|
+
# `select-window` when the session is already a window inside
|
|
775
|
+
# Omega; otherwise we switch the client to that session.
|
|
776
|
+
if action.startswith("attach:"):
|
|
777
|
+
target = action.split(":", 1)[1]
|
|
778
|
+
# If the target is a window of Omega, just select it. Else
|
|
779
|
+
# switch-client to that session (works under nested tmux).
|
|
780
|
+
wnames = subprocess.run(
|
|
781
|
+
["tmux", "list-windows", "-t", "Omega", "-F", "#W"],
|
|
782
|
+
capture_output=True, text=True,
|
|
783
|
+
)
|
|
784
|
+
if (wnames.returncode == 0
|
|
785
|
+
and target in (wnames.stdout or "").splitlines()):
|
|
786
|
+
subprocess.run(["tmux", "select-window",
|
|
787
|
+
"-t", f"Omega:{target}"])
|
|
788
|
+
else:
|
|
789
|
+
# Switch to the foreign session. Use switch-client (works
|
|
790
|
+
# when we're already inside a tmux client) or fall back to
|
|
791
|
+
# attach for non-tmux contexts.
|
|
792
|
+
rc = subprocess.run(
|
|
793
|
+
["tmux", "switch-client", "-t", target],
|
|
794
|
+
capture_output=True,
|
|
795
|
+
).returncode
|
|
796
|
+
if rc != 0:
|
|
797
|
+
_run_inline(f"echo ' cannot attach to {target} — is it alive?' "
|
|
798
|
+
f"&& tmux has-session -t {target} && "
|
|
799
|
+
f"echo ' tmux says: yes' || echo ' tmux says: no'",
|
|
800
|
+
shell=True)
|
|
801
|
+
continue
|
|
802
|
+
# v0.19.39 — "+ New" actions: kill existing chat window first so
|
|
803
|
+
# the user gets a clean fresh session.
|
|
804
|
+
if action in ("open:aisb", "open:aisb:new"):
|
|
805
|
+
force = (action == "open:aisb:new")
|
|
613
806
|
persona = HOME / "Agentik_SSOT" / "agents" / "aisb" / "CLAUDE.md"
|
|
614
807
|
ctx_dir = tmux._ensure_chat_context_dir(HOME, "aisb-master", persona)
|
|
615
808
|
tmux.spawn_chat_in_omega(
|
|
616
809
|
"aisb", ctx_dir=ctx_dir, run_command="claude",
|
|
617
|
-
force_replace=
|
|
810
|
+
force_replace=force,
|
|
618
811
|
)
|
|
619
812
|
subprocess.run(["tmux", "select-window", "-t", "Omega:aisb"])
|
|
620
813
|
continue
|
|
621
|
-
if action
|
|
814
|
+
if action in ("open:hermes", "open:hermes:new"):
|
|
815
|
+
force = (action == "open:hermes:new")
|
|
622
816
|
persona = HOME / "Agentik_SSOT" / "docs" / "LAYERS.md"
|
|
623
817
|
ctx_dir = tmux._ensure_chat_context_dir(HOME, "hermes", persona)
|
|
624
818
|
try:
|
|
@@ -632,10 +826,18 @@ def _arrow_menu() -> int:
|
|
|
632
826
|
if api_key else "claude")
|
|
633
827
|
tmux.spawn_chat_in_omega(
|
|
634
828
|
"hermes", ctx_dir=ctx_dir, run_command=run_cmd,
|
|
635
|
-
force_replace=
|
|
829
|
+
force_replace=force,
|
|
636
830
|
)
|
|
637
831
|
subprocess.run(["tmux", "select-window", "-t", "Omega:hermes"])
|
|
638
832
|
continue
|
|
833
|
+
if action == "audit:run":
|
|
834
|
+
audit_id = _prompt("audit id (codeaudit, uiuxaudit, flowaudit, ...)")
|
|
835
|
+
if audit_id:
|
|
836
|
+
_run_inline([OMEGA_BIN, "audit", audit_id])
|
|
837
|
+
continue
|
|
838
|
+
# (v0.19.31 open:aisb / open:hermes legacy handlers removed in
|
|
839
|
+
# v0.19.39 — the new unified handlers above (which accept both
|
|
840
|
+
# plain and `:new` variants) cover the same surface.)
|
|
639
841
|
if action == "switch:provider":
|
|
640
842
|
_run_inline([OMEGA_BIN, "switch"]); continue
|
|
641
843
|
if action == "genesis:new":
|
package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313-pytest-8.4.2.pyc
CHANGED
|
Binary file
|
|
Binary file
|
package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313-pytest-8.4.2.pyc
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313-pytest-8.4.2.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -48,6 +48,7 @@ class TestInstallerStepList(unittest.TestCase):
|
|
|
48
48
|
# name (the human label) → step function name
|
|
49
49
|
("36-tmux-config", "step_tmux_config"),
|
|
50
50
|
("37-hermes-brief", "step_hermes_brief"),
|
|
51
|
+
("38-personas", "step_personas"),
|
|
51
52
|
("59-hermes-session", "step_hermes_session"),
|
|
52
53
|
]
|
|
53
54
|
|
|
@@ -497,5 +498,134 @@ class TestPostInstallCardSurfacesEntryPoints(unittest.TestCase):
|
|
|
497
498
|
self.assertIn("tmux", text)
|
|
498
499
|
|
|
499
500
|
|
|
501
|
+
# ---------------------------------------------------------------------------
|
|
502
|
+
# step_personas (v0.19.38) — eagerly seeds canonical OMEGAOS-CONTEXT.md +
|
|
503
|
+
# every LLM's expected persona filename for AISB-master + Hermès chats,
|
|
504
|
+
# so the user does NOT have to spawn a chat once before personas exist.
|
|
505
|
+
# ---------------------------------------------------------------------------
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class TestStepPersonasBehavior(unittest.TestCase):
|
|
509
|
+
"""Without this step, personas materialize lazily on first chat spawn —
|
|
510
|
+
`omega doctor` shows them missing and the operator can't pre-edit the
|
|
511
|
+
canonical. This locks in eager seeding."""
|
|
512
|
+
|
|
513
|
+
def test_personas_land_in_both_chat_contexts(self):
|
|
514
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
515
|
+
home = Path(tmp) / "Omega"
|
|
516
|
+
home.mkdir()
|
|
517
|
+
# Mirror the engine into OMEGA_HOME the way step_structure does.
|
|
518
|
+
engine_src = REPO_ROOT / "omega" / "Agentik_Engine"
|
|
519
|
+
engine_dst = home / "Agentik_Engine"
|
|
520
|
+
engine_dst.mkdir()
|
|
521
|
+
for entry in engine_src.iterdir():
|
|
522
|
+
if entry.name in {"tests", "__pycache__"}:
|
|
523
|
+
continue
|
|
524
|
+
if entry.is_dir():
|
|
525
|
+
import shutil
|
|
526
|
+
shutil.copytree(entry, engine_dst / entry.name)
|
|
527
|
+
else:
|
|
528
|
+
(engine_dst / entry.name).write_bytes(entry.read_bytes())
|
|
529
|
+
|
|
530
|
+
old_home = os.environ.get("OMEGA_HOME")
|
|
531
|
+
os.environ["OMEGA_HOME"] = str(home)
|
|
532
|
+
sys.path.insert(0, str(engine_dst))
|
|
533
|
+
try:
|
|
534
|
+
# Reload the engine modules under the new OMEGA_HOME.
|
|
535
|
+
import importlib
|
|
536
|
+
import omega_engine.personas as P
|
|
537
|
+
importlib.reload(P)
|
|
538
|
+
|
|
539
|
+
# Mirror what step_personas does (the heredoc body).
|
|
540
|
+
P.ensure_canonical(home)
|
|
541
|
+
for label in ("aisb-master", "hermes"):
|
|
542
|
+
ctx = home / "Agentik_Coding" / "chat-contexts" / label
|
|
543
|
+
P.write_all_personas(home, ctx)
|
|
544
|
+
finally:
|
|
545
|
+
if old_home is None:
|
|
546
|
+
os.environ.pop("OMEGA_HOME", None)
|
|
547
|
+
else:
|
|
548
|
+
os.environ["OMEGA_HOME"] = old_home
|
|
549
|
+
sys.path.remove(str(engine_dst))
|
|
550
|
+
|
|
551
|
+
# Canonical lands at the SSOT path.
|
|
552
|
+
canon = home / "Agentik_SSOT" / "personas" / "OMEGAOS-CONTEXT.md"
|
|
553
|
+
self.assertTrue(canon.is_file(),
|
|
554
|
+
f"canonical must exist at {canon} — operator must be able "
|
|
555
|
+
"to edit it BEFORE the first chat spawn")
|
|
556
|
+
self.assertGreater(canon.stat().st_size, 500,
|
|
557
|
+
"canonical context looks suspiciously small")
|
|
558
|
+
|
|
559
|
+
# Per-LLM mirrors land in BOTH chat contexts.
|
|
560
|
+
for label in ("aisb-master", "hermes"):
|
|
561
|
+
ctx = home / "Agentik_Coding" / "chat-contexts" / label
|
|
562
|
+
for fname in ("CLAUDE.md", "GEMINI.md", "AGENTS.md",
|
|
563
|
+
"QWEN.md", "HERMES.md", "CONVENTIONS.md"):
|
|
564
|
+
self.assertTrue((ctx / fname).is_file(),
|
|
565
|
+
f"missing {ctx}/{fname} — chat context not fully seeded")
|
|
566
|
+
# Subdir personas (.opencode shared by opencode/openrouter/deepseek).
|
|
567
|
+
self.assertTrue((ctx / ".opencode" / "CONTEXT.md").is_file(),
|
|
568
|
+
f"missing .opencode/CONTEXT.md in {ctx}")
|
|
569
|
+
self.assertTrue((ctx / ".continue" / "CONTEXT.md").is_file(),
|
|
570
|
+
f"missing .continue/CONTEXT.md in {ctx}")
|
|
571
|
+
|
|
572
|
+
def test_step_personas_heredoc_calls_required_helpers(self):
|
|
573
|
+
"""Lock in the heredoc body — if someone renames or removes
|
|
574
|
+
ensure_canonical / write_all_personas the step still has to call
|
|
575
|
+
them or the install silently stops seeding."""
|
|
576
|
+
body = STEPS_SH.read_text()
|
|
577
|
+
m = re.search(r"^step_personas\(\) \{(.+?)^\}", body,
|
|
578
|
+
re.DOTALL | re.MULTILINE)
|
|
579
|
+
self.assertIsNotNone(m, "step_personas function not found in steps.sh")
|
|
580
|
+
fn_body = m.group(1)
|
|
581
|
+
for name in ("ensure_canonical", "write_all_personas"):
|
|
582
|
+
self.assertIn(name, fn_body,
|
|
583
|
+
f"step_personas must call {name}() — without it the install "
|
|
584
|
+
"stops seeding personas")
|
|
585
|
+
for label in ("aisb-master", "hermes"):
|
|
586
|
+
self.assertIn(label, fn_body,
|
|
587
|
+
f"step_personas must seed chat-contexts/{label}/")
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
# ---------------------------------------------------------------------------
|
|
591
|
+
# v0.19.38 — `--` separator must NOT raise "unknown argument".
|
|
592
|
+
# Repro: `npx -y @agentikos/omega-os@latest -- --full` lands in install.sh
|
|
593
|
+
# as `install.sh -- --full`, and the case-block used to reject `--`.
|
|
594
|
+
# ---------------------------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
class TestDashDashSeparatorAccepted(unittest.TestCase):
|
|
598
|
+
"""Two layers must accept `--`:
|
|
599
|
+
1. bin/omega-os.js strips it before exec'ing bash.
|
|
600
|
+
2. install.sh accepts it as a no-op as defense-in-depth."""
|
|
601
|
+
|
|
602
|
+
def test_install_sh_has_dash_dash_no_op_case(self):
|
|
603
|
+
if not INSTALL_SH.is_file():
|
|
604
|
+
self.skipTest("install.sh not in repo")
|
|
605
|
+
text = INSTALL_SH.read_text()
|
|
606
|
+
# The case-block must explicitly match `--`. Use re.search with
|
|
607
|
+
# MULTILINE so `^` matches each line start.
|
|
608
|
+
self.assertTrue(
|
|
609
|
+
re.search(r"^\s*--\)\s+shift\s*;;", text, re.MULTILINE),
|
|
610
|
+
"install.sh must accept `--` as a no-op — without it "
|
|
611
|
+
"`bash install.sh -- --full` dies with `unknown argument: --`")
|
|
612
|
+
|
|
613
|
+
def test_bin_launcher_filters_dash_dash(self):
|
|
614
|
+
bin_js = REPO_ROOT / "bin" / "omega-os.js"
|
|
615
|
+
if not bin_js.is_file():
|
|
616
|
+
self.skipTest("bin/omega-os.js not in repo")
|
|
617
|
+
text = bin_js.read_text()
|
|
618
|
+
# The filter must drop the literal `--` so the bash installer's
|
|
619
|
+
# case-block isn't even reached for the npx-injected separator.
|
|
620
|
+
# Match `.filter(...)` containing the literal "--" on the same line.
|
|
621
|
+
# Use DOTALL-free single-line scan, just look for the two anchors
|
|
622
|
+
# together: `.filter(` and `"--"` within ~80 chars.
|
|
623
|
+
self.assertTrue(
|
|
624
|
+
re.search(r'\.filter\(.{0,80}"--"', text),
|
|
625
|
+
"bin/omega-os.js must filter the `--` separator out of argv "
|
|
626
|
+
"before passing to install.sh — pattern `.filter(... \"--\")` "
|
|
627
|
+
"not found")
|
|
628
|
+
|
|
629
|
+
|
|
500
630
|
if __name__ == "__main__":
|
|
501
631
|
unittest.main()
|