@agentikos/omega-os 0.19.17 → 0.19.19

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.
@@ -21,16 +21,18 @@ step_preflight() {
21
21
 
22
22
  # --- 10 -----------------------------------------------------------------------
23
23
  step_system_deps() {
24
- # `whiptail` powers `omega` (the interactive menu — the post-install entry
25
- # point). On Debian/Ubuntu it lives in the `whiptail` package, on RHEL/Fedora
26
- # in `newt`, on macOS in `newt` via Homebrew. We add it to the base set so a
27
- # fresh Mac doesn't end up with a silent `omega` command.
24
+ # System deps:
25
+ # - whiptail/newt back-up menu (`omega menu`)
26
+ # - fzf — primary picker for the new session manager (`omega`)
27
+ # and required by the tmux-claude install script too
28
+ # whiptail lives in `whiptail` (Debian/Ubuntu), `newt` (RHEL/Fedora),
29
+ # `newt` (Homebrew). fzf is `fzf` on every platform.
28
30
  local pkgs="python3 git tmux sqlite3 jq curl"
29
31
  case "$OMEGA_PKG" in
30
- apt) sudo apt-get update -qq && sudo apt-get install -y -qq $pkgs whiptail python3-venv python3-yaml ;;
31
- dnf) sudo dnf install -y -q $pkgs newt python3-pyyaml ;;
32
- brew) brew install $pkgs newt 2>/dev/null || true ;;
33
- *) err "install manually: $pkgs (and whiptail/newt for the menu)"; return 1 ;;
32
+ apt) sudo apt-get update -qq && sudo apt-get install -y -qq $pkgs whiptail fzf python3-venv python3-yaml ;;
33
+ dnf) sudo dnf install -y -q $pkgs newt fzf python3-pyyaml ;;
34
+ brew) brew install $pkgs newt fzf 2>/dev/null || true ;;
35
+ *) err "install manually: $pkgs (and whiptail/newt + fzf for the menu)"; return 1 ;;
34
36
  esac
35
37
 
36
38
  # PyYAML — required by every install-side helper that reads the manifest
@@ -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.17"
191
+ __version__ = "0.19.19"
192
192
 
193
193
  __all__ = [
194
194
  "__version__",
@@ -2716,24 +2716,25 @@ def cmd_tmux(args: argparse.Namespace) -> int:
2716
2716
 
2717
2717
 
2718
2718
  def cmd_aisb(args: argparse.Namespace) -> int:
2719
- """`omega aisb` — talk to the AISB master agent without Telegram.
2719
+ """`omega aisb` — straight to the AISB master Claude Code session.
2720
2720
 
2721
- Two sub-commands:
2722
- chat — spawn (or attach to) the AISB-chat tmux session
2723
- chat-loop — the REPL loop itself (run inside the tmux session)
2721
+ v0.19.19 — `omega aisb` (no subcommand) now does spawn+attach in one
2722
+ shot, identical to selecting "+ New AISB" from the `omega` session
2723
+ manager. Same Claude Code TUI, same persona via CLAUDE.md.
2724
+
2725
+ Sub-commands:
2726
+ chat — explicit alias for the default (back-compat)
2727
+ chat-loop — the legacy Python REPL (used by dispatched workers
2728
+ that want a programmatic single-turn API)
2724
2729
  """
2725
2730
  from omega_engine import tmux
2726
- sub = args.aisb_cmd or "chat"
2727
- home = _omega_home()
2728
- if sub == "chat":
2729
- name = "AISB-chat"
2730
- if tmux.is_alive(name):
2731
- print(f" session already exists: {name}")
2732
- else:
2733
- tmux.spawn_aisb_chat(home)
2734
- print(f" spawned: {name}")
2735
- print(f" attach: {tmux.attach_command(name)}")
2736
- return 0
2731
+ sub = getattr(args, "aisb_cmd", None) or "chat"
2732
+ if sub in ("chat", None):
2733
+ return _attach_or_spawn_chat(
2734
+ "AISB-chat",
2735
+ spawn_fn=tmux.spawn_aisb_chat,
2736
+ label="AISB master (Claude Code on Max OAuth)",
2737
+ )
2737
2738
  if sub == "chat-loop":
2738
2739
  from omega_engine.aisb_chat import run_chat_loop
2739
2740
  return run_chat_loop()
@@ -2783,21 +2784,149 @@ def _attach_or_spawn_chat(session_name: str, *,
2783
2784
  return 0 # unreachable (execvp replaced the process)
2784
2785
 
2785
2786
 
2786
- def cmd_menu(_args: argparse.Namespace) -> int:
2787
- """`omega` (no args) open the AISB master chat session in tmux.
2787
+ def _session_age(unix_ts: int) -> str:
2788
+ """Pretty-print 'now' / '5m ago' / '2h ago' / '3d ago'."""
2789
+ import time as _t
2790
+ delta = max(0, int(_t.time()) - int(unix_ts))
2791
+ if delta < 60:
2792
+ return "now"
2793
+ if delta < 3600:
2794
+ return f"{delta // 60}m ago"
2795
+ if delta < 86400:
2796
+ return f"{delta // 3600}h ago"
2797
+ return f"{delta // 86400}d ago"
2798
+
2788
2799
 
2789
- v0.19.15 changed the default: typing `omega` used to open the whiptail
2790
- menu, which Gareth correctly called bad UX ("type `omega`, talk to my
2791
- master agent"). Now bare `omega` attaches to the always-on `AISB-chat`
2792
- tmux session, spawning it on demand. The whiptail menu lives at
2793
- `omega menu`.
2800
+ def cmd_menu(_args: argparse.Namespace) -> int:
2801
+ """`omega` (no args) open the OmegaOS session manager.
2802
+
2803
+ v0.19.19 redesign per the user's spec: bare `omega` no longer
2804
+ auto-attaches to AISB-chat. Instead it shows a fzf-driven menu:
2805
+
2806
+ ┌─ Omega — Session Manager ──────────────────┐
2807
+ │ > AISB-chat now │
2808
+ │ Hermes-chat 5m ago │
2809
+ │ Causio-oracle 2h ago │
2810
+ │ ───────────────────────────────────────── │
2811
+ │ + New Claude Code session (AISB master) │
2812
+ │ + New Hermès session │
2813
+ └────────────────────────────────────────────┘
2814
+
2815
+ Pick a session → attach. Pick "+ New …" → spawn + attach. Esc / q
2816
+ → exit cleanly.
2817
+
2818
+ Direct shortcuts kept:
2819
+ * `omega aisb` → straight to AISB-chat (no menu)
2820
+ * `omega hermes` → straight to Hermes-chat (no menu)
2821
+ * `omega menu` → legacy whiptail picker (advanced ops)
2794
2822
  """
2823
+ import os
2824
+ import shutil
2825
+ import subprocess
2795
2826
  from omega_engine import tmux
2796
- return _attach_or_spawn_chat(
2797
- "AISB-chat",
2798
- spawn_fn=tmux.spawn_aisb_chat,
2799
- label="AISB master chat",
2800
- )
2827
+
2828
+ # If we're already inside a tmux session, instruct the user — we can't
2829
+ # take over the current pane from a subprocess, but `omega aisb` /
2830
+ # `omega hermes` from inside tmux still print switch-client instructions.
2831
+ if os.environ.get("TMUX"):
2832
+ print(" You're already inside tmux. Use Ctrl+b z (tmux-claude")
2833
+ print(" session manager) or one of:")
2834
+ print(" tmux switch-client -t AISB-chat")
2835
+ print(" tmux switch-client -t Hermes-chat")
2836
+ return 0
2837
+
2838
+ if not shutil.which("tmux"):
2839
+ print(" tmux not installed — `brew install tmux` (macOS) or "
2840
+ "`apt-get install tmux` (Linux)")
2841
+ return 2
2842
+
2843
+ sessions = tmux.list_sessions(_omega_home())
2844
+ sessions.sort(key=lambda s: -int(s.created))
2845
+
2846
+ NEW_AISB = "__new_aisb__"
2847
+ NEW_HERMES = "__new_hermes__"
2848
+
2849
+ lines: list[tuple[str, str]] = [] # (display, action_key)
2850
+ if sessions:
2851
+ for s in sessions:
2852
+ attach_mark = " *" if s.attached else " "
2853
+ line = f"{attach_mark} {s.name:<32} {_session_age(s.created):<10} {s.category}"
2854
+ lines.append((line, s.name))
2855
+ lines.append(("──────────────────────────────────────────────",
2856
+ "__separator__"))
2857
+ lines.append((" + New Claude Code session (AISB master)", NEW_AISB))
2858
+ lines.append((" + New Hermès session", NEW_HERMES))
2859
+
2860
+ chosen_key: str | None = None
2861
+ if shutil.which("fzf"):
2862
+ # fzf path — prettiest UX. Header shows the title, separator line
2863
+ # is non-selectable (we filter it after).
2864
+ fzf_input = "\n".join(disp for disp, _ in lines)
2865
+ try:
2866
+ proc = subprocess.run(
2867
+ ["fzf",
2868
+ "--prompt=omega › ",
2869
+ "--header=Omega Session Manager — Enter to attach, Esc to quit",
2870
+ "--layout=reverse",
2871
+ "--height=70%",
2872
+ "--border",
2873
+ "--no-multi"],
2874
+ input=fzf_input, capture_output=True, text=True,
2875
+ )
2876
+ if proc.returncode == 0:
2877
+ pick = proc.stdout.strip()
2878
+ for disp, key in lines:
2879
+ if disp == pick and key != "__separator__":
2880
+ chosen_key = key
2881
+ break
2882
+ except Exception as exc: # noqa: BLE001
2883
+ print(f" fzf failed: {exc}")
2884
+ return 2
2885
+ else:
2886
+ # Plain stdin fallback — numbered list.
2887
+ print()
2888
+ print(" Omega Session Manager")
2889
+ for i, (disp, _) in enumerate(lines, 1):
2890
+ print(f" {i:2}) {disp}")
2891
+ print()
2892
+ try:
2893
+ raw = input(" pick a number (Enter to quit): ").strip()
2894
+ except (EOFError, KeyboardInterrupt):
2895
+ return 0
2896
+ if not raw:
2897
+ return 0
2898
+ try:
2899
+ n = int(raw)
2900
+ except ValueError:
2901
+ print(f" not a number: {raw!r}")
2902
+ return 0
2903
+ if 1 <= n <= len(lines):
2904
+ chosen_key = lines[n - 1][1]
2905
+ if chosen_key == "__separator__":
2906
+ chosen_key = None
2907
+
2908
+ if chosen_key is None:
2909
+ return 0
2910
+ if chosen_key == NEW_AISB:
2911
+ return _attach_or_spawn_chat(
2912
+ "AISB-chat",
2913
+ spawn_fn=tmux.spawn_aisb_chat,
2914
+ label="AISB master (Claude Code on Max OAuth)",
2915
+ )
2916
+ if chosen_key == NEW_HERMES:
2917
+ return _attach_or_spawn_chat(
2918
+ "Hermes-chat",
2919
+ spawn_fn=tmux.spawn_hermes_chat,
2920
+ label="Hermès (Claude Code on Anthropic API)",
2921
+ )
2922
+ # Otherwise it's an existing session name — just attach.
2923
+ print(f" attaching to {chosen_key}…")
2924
+ try:
2925
+ os.execvp("tmux", ["tmux", "attach", "-t", chosen_key])
2926
+ except FileNotFoundError:
2927
+ print(" tmux not on PATH")
2928
+ return 2
2929
+ return 0
2801
2930
 
2802
2931
 
2803
2932
  def cmd_menu_whiptail(_args: argparse.Namespace) -> int:
@@ -231,42 +231,99 @@ def spawn_worker(project: str, task: str, *,
231
231
  return name
232
232
 
233
233
 
234
+ def _ensure_chat_context_dir(home: Path, label: str,
235
+ persona_md_path: Path) -> Path:
236
+ """Create (idempotent) a dedicated Claude Code project dir for an
237
+ OmegaOS chat session.
238
+
239
+ Drops ``CLAUDE.md`` (the persona) so Claude Code auto-loads it as
240
+ project context when the user runs `claude` in this dir. Returns
241
+ the path.
242
+ """
243
+ ctx_dir = home / "Agentik_Coding" / "chat-contexts" / label
244
+ ctx_dir.mkdir(parents=True, exist_ok=True)
245
+ target = ctx_dir / "CLAUDE.md"
246
+ if persona_md_path.is_file():
247
+ # Mirror the upstream persona file so refreshes propagate.
248
+ target.write_text(persona_md_path.read_text())
249
+ elif not target.exists():
250
+ target.write_text(
251
+ f"# {label} chat context\n\n"
252
+ f"(no persona file found — using default Claude Code behaviour)\n"
253
+ )
254
+ # Also drop a .gitignore so this transient dir never gets committed
255
+ # by accident if the user `git init`s it.
256
+ gi = ctx_dir / ".gitignore"
257
+ if not gi.exists():
258
+ gi.write_text("*\n!.gitignore\n!CLAUDE.md\n")
259
+ return ctx_dir
260
+
261
+
234
262
  def spawn_aisb_chat(omega_home: str | Path | None = None) -> str:
235
- """Spawn the AISB master chat tmux session.
263
+ """Spawn the AISB master chat tmux session — REAL Claude Code TUI.
264
+
265
+ v0.19.18 — instead of running our Python REPL (`omega aisb chat-loop`),
266
+ we now run the canonical `claude` CLI inside a dedicated project dir
267
+ seeded with the AISB master persona (``Agentik_SSOT/agents/aisb/CLAUDE.md``).
268
+ User sees the SAME interactive Claude Code TUI they get with bare
269
+ `claude`, but pre-loaded with AISB's identity, the L3 orchestrator
270
+ role, and full OmegaOS context.
236
271
 
237
- Runs ``omega aisb chat-loop`` inside REPL conversation with the
238
- AISB master agent on Claude Max OAuth. This is what bare `omega`
239
- attaches to (v0.19.15+).
272
+ Auth: Claude Max OAuth (inherited from the user's shell env, no env
273
+ override needed `claude` reads `~/.claude/.credentials.json`).
240
274
 
241
- v0.19.17 — explicitly cwd=$HOME. Without this the session inherited
242
- the parent process's cwd, which could be the npm/_npx cache dir; if
243
- that dir got cleaned (or `rm -rf ~/Omega` happened later), every
244
- subprocess (including `claude -p`) crashed with
245
- "The current working directory was deleted".
275
+ cwd=$HOME is the legacy crash fix (spawn() default would inherit
276
+ the npm/_npx cache dir which can disappear); the `claude` process
277
+ itself runs with cwd = the context dir we just created.
246
278
  """
247
279
  name = "AISB-chat"
248
280
  home = Path(omega_home or os.environ.get("OMEGA_HOME")
249
281
  or Path.home() / "Omega")
250
- omega_bin = home / "Agentik_Tools" / "bin" / "omega"
251
- spawn(name, command=f"{omega_bin} aisb chat-loop",
282
+ persona = home / "Agentik_SSOT" / "agents" / "aisb" / "CLAUDE.md"
283
+ ctx_dir = _ensure_chat_context_dir(home, "aisb-master", persona)
284
+ spawn(name, command=f"cd {ctx_dir} && exec claude",
252
285
  cwd=str(Path.home()))
253
286
  return name
254
287
 
255
288
 
256
289
  def spawn_hermes_chat(omega_home: str | Path | None = None) -> str:
257
- """Spawn the Hermès meta-companion chat tmux session.
258
-
259
- Runs ``omega hermes chat-loop`` — REPL conversation with Hermès on
260
- Anthropic API (separate budget from AISB's Max OAuth). This is what
261
- `omega hermes` attaches to (v0.19.15+). Spawned with cwd=$HOME for
262
- the same reason as ``spawn_aisb_chat`` (avoid deleted-cwd crash).
290
+ """Spawn the Hermès chat tmux session — REAL Claude Code TUI, but on
291
+ Anthropic API (Hermès's own paid key, budget-isolated from Max OAuth).
292
+
293
+ Mirrors ``spawn_aisb_chat`` for the L2 companion: drops a Hermès
294
+ persona ``CLAUDE.md`` into a dedicated context dir, then runs
295
+ `claude` there with ``ANTHROPIC_API_KEY`` exported from the vault.
296
+ Setting the env var makes Claude Code use the API key instead of
297
+ the Max OAuth — same TUI, different billing surface.
263
298
  """
264
299
  name = "Hermes-chat"
265
300
  home = Path(omega_home or os.environ.get("OMEGA_HOME")
266
301
  or Path.home() / "Omega")
267
- omega_bin = home / "Agentik_Tools" / "bin" / "omega"
268
- spawn(name, command=f"{omega_bin} hermes chat-loop",
269
- cwd=str(Path.home()))
302
+ persona = home / "Agentik_SSOT" / "docs" / "LAYERS.md" # Hermès reads architecture as persona
303
+ ctx_dir = _ensure_chat_context_dir(home, "hermes", persona)
304
+ # Resolve Anthropic key from vault → fallback to existing env.
305
+ try:
306
+ from omega_engine.vault import vault_read
307
+ api_key = (vault_read(home, "ANTHROPIC_API_KEY_HERMES") or "").strip()
308
+ except Exception: # noqa: BLE001
309
+ api_key = ""
310
+ if not api_key:
311
+ api_key = (os.environ.get("ANTHROPIC_API_KEY") or "").strip()
312
+ # If no key, fall back to Max OAuth (Hermès will share AISB's
313
+ # billing — not ideal but better than failing). Surface a marker
314
+ # file in the context dir so the user sees the warning on first launch.
315
+ if not api_key:
316
+ (ctx_dir / "NO_ANTHROPIC_KEY.md").write_text(
317
+ "# Hermès — no Anthropic API key wired\n\n"
318
+ "Set `ANTHROPIC_API_KEY_HERMES` in the vault to use Hermès's\n"
319
+ "own paid Anthropic budget instead of the AISB Max OAuth.\n\n"
320
+ " omega vault write ANTHROPIC_API_KEY_HERMES sk-ant-...\n"
321
+ )
322
+ cmd = f"cd {ctx_dir} && exec claude"
323
+ else:
324
+ cmd = (f"cd {ctx_dir} && "
325
+ f"ANTHROPIC_API_KEY={api_key} exec claude")
326
+ spawn(name, command=cmd, cwd=str(Path.home()))
270
327
  return name
271
328
 
272
329
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omega-engine"
3
- version = "0.19.17"
3
+ version = "0.19.19"
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"
@@ -1 +1 @@
1
- 0.19.17
1
+ 0.19.19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentikos/omega-os",
3
- "version": "0.19.17",
3
+ "version": "0.19.19",
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"