@agentikos/omega-os 0.19.42 → 0.19.44

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 (42) hide show
  1. package/bootstrap/lib/__pycache__/llm-clis.cpython-313.pyc +0 -0
  2. package/bootstrap/lib/common.sh +39 -9
  3. package/bootstrap/lib/llm-clis.py +6 -0
  4. package/bootstrap/lib/manifest-helpers.py +110 -0
  5. package/bootstrap/lib/steps.sh +253 -28
  6. package/bootstrap/templates/aisb/CLAUDE.md +13 -0
  7. package/install.sh +8 -2
  8. package/omega/Agentik_Engine/omega_engine/__init__.py +1 -1
  9. package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
  10. package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
  11. package/omega/Agentik_Engine/omega_engine/__pycache__/hermes.cpython-313.pyc +0 -0
  12. package/omega/Agentik_Engine/omega_engine/__pycache__/paperclip_bridge.cpython-313.pyc +0 -0
  13. package/omega/Agentik_Engine/omega_engine/__pycache__/personas.cpython-313.pyc +0 -0
  14. package/omega/Agentik_Engine/omega_engine/__pycache__/provider.cpython-313.pyc +0 -0
  15. package/omega/Agentik_Engine/omega_engine/__pycache__/tmux.cpython-313.pyc +0 -0
  16. package/omega/Agentik_Engine/omega_engine/__pycache__/tui.cpython-313.pyc +0 -0
  17. package/omega/Agentik_Engine/omega_engine/cli.py +44 -7
  18. package/omega/Agentik_Engine/omega_engine/hermes.py +43 -1
  19. package/omega/Agentik_Engine/omega_engine/paperclip_bridge.py +22 -0
  20. package/omega/Agentik_Engine/omega_engine/personas.py +11 -3
  21. package/omega/Agentik_Engine/omega_engine/provider.py +18 -3
  22. package/omega/Agentik_Engine/omega_engine/tmux.py +41 -21
  23. package/omega/Agentik_Engine/omega_engine/tui.py +8 -7
  24. package/omega/Agentik_Engine/pyproject.toml +1 -1
  25. package/omega/Agentik_Engine/tests/__pycache__/test_install_steps_v0_19_43.cpython-313-pytest-8.4.2.pyc +0 -0
  26. package/omega/Agentik_Engine/tests/__pycache__/test_install_steps_v0_19_43.cpython-313.pyc +0 -0
  27. package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313-pytest-8.4.2.pyc +0 -0
  28. package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313.pyc +0 -0
  29. package/omega/Agentik_Engine/tests/__pycache__/test_tmux_palette.cpython-313-pytest-8.4.2.pyc +0 -0
  30. package/omega/Agentik_Engine/tests/__pycache__/test_tmux_palette.cpython-313.pyc +0 -0
  31. package/omega/Agentik_Engine/tests/__pycache__/test_v19_43_fixes.cpython-313-pytest-8.4.2.pyc +0 -0
  32. package/omega/Agentik_Engine/tests/__pycache__/test_v19_43_fixes.cpython-313.pyc +0 -0
  33. package/omega/Agentik_Engine/tests/test_install_steps_v0_19_43.py +242 -0
  34. package/omega/Agentik_Engine/tests/test_installer_wiring.py +128 -1
  35. package/omega/Agentik_Engine/tests/test_tmux_palette.py +51 -0
  36. package/omega/Agentik_Engine/tests/test_v19_43_fixes.py +265 -0
  37. package/omega/Agentik_SSOT/VERSION +1 -1
  38. package/omega/Agentik_SSOT/docs/AUDIT-V0.19.43.md +92 -0
  39. package/omega/Agentik_SSOT/rules/audit-gates.md +2 -2
  40. package/omega/Agentik_SSOT/rules/constitution.md +18 -0
  41. package/omega/Agentik_SSOT/rules/three-laws.md +2 -0
  42. package/package.json +1 -1
@@ -37,8 +37,14 @@ detect_os() {
37
37
  *) die "unsupported OS: $(uname -s)" ;;
38
38
  esac
39
39
  if [ "$OMEGA_OS" = "linux" ]; then
40
+ # Order matters: prefer the manager actually managing the distro.
41
+ # apt (Debian/Ubuntu) → dnf (RHEL/Fedora) → pacman (Arch/Manjaro)
42
+ # → apk (Alpine) → zypper (openSUSE/SLES) → unknown.
40
43
  if have apt-get; then OMEGA_PKG="apt"
41
44
  elif have dnf; then OMEGA_PKG="dnf"
45
+ elif have pacman; then OMEGA_PKG="pacman"
46
+ elif have apk; then OMEGA_PKG="apk"
47
+ elif have zypper; then OMEGA_PKG="zypper"
42
48
  else OMEGA_PKG="unknown"; fi
43
49
  else
44
50
  OMEGA_PKG="brew"
@@ -89,7 +95,25 @@ record_state_version() {
89
95
  # card prints normally.
90
96
  run_step() {
91
97
  local name="$1" fn="$2"
92
- if step_done "$name" && [ "${FORCE:-0}" != "1" ]; then
98
+ # v0.19.44 code-deploy steps NEVER skip when the bundled version
99
+ # differs from the installed version, even if state file marks done.
100
+ # Without this, a stale `.install-state` entry blocks step_structure
101
+ # from refreshing cli.py — the bug the user hit on v0.19.43.
102
+ local _force_step=0
103
+ case "$name" in
104
+ *structure*|*aisb-suite*|*audit-skills*|*engine*|*personas*)
105
+ local _installed_v=""
106
+ [ -f "$OMEGA_HOME/Agentik_SSOT/VERSION" ] \
107
+ && _installed_v="$(cat "$OMEGA_HOME/Agentik_SSOT/VERSION" 2>/dev/null || echo '')"
108
+ if [ -n "$_installed_v" ] \
109
+ && [ "$_installed_v" != "${OMEGA_BUNDLED_VERSION:-}" ]; then
110
+ _force_step=1
111
+ fi
112
+ ;;
113
+ esac
114
+ if step_done "$name" \
115
+ && [ "${FORCE:-0}" != "1" ] \
116
+ && [ "$_force_step" != "1" ]; then
93
117
  if [ "${INSTALL_FULL:-0}" = "1" ]; then
94
118
  _full_progress "$name" "skip"
95
119
  return 0
@@ -97,6 +121,9 @@ run_step() {
97
121
  log "${C_DIM}-- skip $name (already done)${C_RST}"
98
122
  return 0
99
123
  fi
124
+ if [ "$_force_step" = "1" ]; then
125
+ log "${C_YELLOW}!${C_RST} force-rerun $name (installed v${_installed_v} ≠ bundled v${OMEGA_BUNDLED_VERSION:-?})"
126
+ fi
100
127
 
101
128
  if [ "${INSTALL_FULL:-0}" = "1" ]; then
102
129
  # Redirect step output to the log; only the progress bar stays on screen.
@@ -423,14 +450,17 @@ omega_version() {
423
450
  install_banner() {
424
451
  local v; v="$(omega_version)"
425
452
  printf '\n'
426
- printf '%s ▄██████▄ ▄▄▄▄███▄▄▄▄ ▄████████ ▄██████▄ ▄████████%s\n' "$C_ORANGE" "$C_RST"
427
- printf '%s ███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
428
- printf '%s ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
429
- printf '%s ███ ███ ███ ███ ███ ▄███▄▄▄ ▄███ ▄███▄▄▄▄██▀%s\n' "$C_ORANGE" "$C_RST"
430
- printf '%s ███ ███ ███ ███ ███ ▀▀███▀▀▀ ▀▀███ ████▄ ▀▀███▀▀▀▀▀ %s\n' "$C_ORANGE" "$C_RST"
431
- printf '%s ███ ███ ███ ███ ███ ███ █▄ ███ ███ ▀███████████%s\n' "$C_ORANGE" "$C_RST"
432
- printf '%s ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
433
- printf '%s ▀██████▀ ▀█ ███ █▀ ██████████ ▀██████▀ ███ ███%s\n' "$C_ORANGE" "$C_RST"
453
+ # v0.19.44 clean blocky A consistent with O/M/E/G shapes. The old A
454
+ # used half-diagonal characters (▄███▄▄▄▄██▀ / ▀▀███▀▀▀▀▀ / ▀███████████)
455
+ # that looked like noise — the new A has a clean horizontal crossbar.
456
+ printf '%s ▄██████▄ ▄▄▄▄███▄▄▄▄ ▄████████ ▄██████▄ ▄████████ %s\n' "$C_ORANGE" "$C_RST"
457
+ printf '%s ███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
458
+ printf '%s ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
459
+ printf '%s ███ ███ ███ ███ ███ ▄███▄▄▄ ▄███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
460
+ printf '%s ███ ███ ███ ███ ███ ▀▀███▀▀▀ ▀▀███ ████▄ ██████████%s\n' "$C_ORANGE" "$C_RST"
461
+ printf '%s ███ ███ ███ ███ ███ ███ █▄ ███ ███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
462
+ printf '%s ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███%s\n' "$C_ORANGE" "$C_RST"
463
+ printf '%s ▀██████▀ ▀█ ███ █▀ ██████████ ▀██████▀ ███ ███%s\n' "$C_ORANGE" "$C_RST"
434
464
  printf '\n'
435
465
  printf ' %sOmega OS%s %sv%s%s · %sagentik-os/OmegaOS%s\n' \
436
466
  "$C_BOLD" "$C_RST" "$C_DIM" "$v" "$C_RST" "$C_DIM" "$C_RST"
@@ -219,6 +219,12 @@ def _resolve_install_cmd(spec: CliSpec) -> list[str] | None:
219
219
  if _have("pip"):
220
220
  return ["pip", "install", "--user", "aider-chat"]
221
221
  return None
222
+ if spec.id == "ollama":
223
+ # Ollama's official installer is a curl-piped shell script —
224
+ # the only supported install path on Linux/macOS. v0.19.43 fix:
225
+ # previously returned None, so the --full installer would refuse
226
+ # to install ollama at all.
227
+ return ["bash", "-c", "curl -fsSL https://ollama.com/install.sh | sh"]
222
228
  return None
223
229
 
224
230
 
@@ -36,6 +36,12 @@ Subcommands
36
36
  telegram-chat-id <manifest> <omega_home>
37
37
  Read ``telegram.group_chat_id`` from the manifest (if set) and
38
38
  persist it through the vault. Returns the resolved id on stdout.
39
+
40
+ providers-from-catalog <omega_home>
41
+ --full mode fallback. Discover available providers from
42
+ Agentik_SSOT/llm-providers/providers-catalog.yaml — include any
43
+ whose first env var is set in the environment OR whose secret
44
+ already exists in the vault. Writes a default router.yaml.
39
45
  """
40
46
  from __future__ import annotations
41
47
 
@@ -100,6 +106,108 @@ def cmd_providers(manifest: str, omega_home: str) -> int:
100
106
  return 0
101
107
 
102
108
 
109
+ def _roles_for_cli(cli: str) -> list[str]:
110
+ """Map an LLM CLI to default ModelRouter roles.
111
+
112
+ Reasoning:
113
+ * claude_code = Anthropic Claude — the canonical executor/dispatcher.
114
+ * gemini_cli = Google Gemini — strong general executor.
115
+ * codex = OpenAI Codex — executor (paid OpenAI API).
116
+ * opencode = multi-provider community CLI — anything goes.
117
+ * Others fall back to a generic "executor" role.
118
+ """
119
+ cli = (cli or "").strip()
120
+ if cli == "claude_code":
121
+ return ["executor", "dispatcher"]
122
+ if cli == "opencode":
123
+ return ["executor", "worker"]
124
+ return ["executor"]
125
+
126
+
127
+ def cmd_providers_from_catalog(omega_home: str) -> int:
128
+ """--full mode fallback: auto-generate router.yaml from the catalog.
129
+
130
+ For every provider in
131
+ ``Agentik_SSOT/llm-providers/providers-catalog.yaml`` whose first
132
+ secret_ref env var is present (in the live env or in the vault),
133
+ write a corresponding entry to ``Agentik_SSOT/providers/router.yaml``.
134
+ Providers with ``secret_refs: []`` (oauth / local) are always
135
+ eligible because they don't need an api key.
136
+ """
137
+ home = Path(omega_home)
138
+ catalog_path = home / "Agentik_SSOT" / "llm-providers" / "providers-catalog.yaml"
139
+ if not catalog_path.exists():
140
+ print(f"no providers catalog at {catalog_path} — skipping")
141
+ return 0
142
+ try:
143
+ catalog = yaml.safe_load(catalog_path.read_text()) or {}
144
+ except yaml.YAMLError as exc:
145
+ print(f"catalog YAML error: {exc}")
146
+ return 0
147
+ entries = catalog.get("providers") or []
148
+ if not entries:
149
+ print(f"catalog at {catalog_path} has no `providers:` block")
150
+ return 0
151
+
152
+ # Vault is best-effort — if the engine isn't importable yet we just
153
+ # rely on os.environ.
154
+ _engine_on_path(home)
155
+ vault_read = None
156
+ try:
157
+ from omega_engine.vault import vault_read as _vr # type: ignore
158
+ vault_read = _vr
159
+ except Exception:
160
+ vault_read = None
161
+
162
+ def _has_secret(env_name: str) -> bool:
163
+ if os.environ.get(env_name):
164
+ return True
165
+ if vault_read is None:
166
+ return False
167
+ try:
168
+ v = vault_read(home, env_name)
169
+ return bool(v and v.strip())
170
+ except Exception:
171
+ return False
172
+
173
+ router: dict = {"providers": []}
174
+ discovered: list[str] = []
175
+ for entry in entries:
176
+ if not isinstance(entry, dict):
177
+ continue
178
+ pid = (entry.get("id") or "").strip()
179
+ if not pid:
180
+ continue
181
+ cli = (entry.get("cli") or "").strip()
182
+ refs = entry.get("secret_refs") or []
183
+ # Providers with no secret_refs at all (oauth / local — ollama,
184
+ # lm_studio, github_copilot, chatgpt_subscription) are eligible
185
+ # without a key check.
186
+ primary = refs[0] if refs else None
187
+ eligible = (primary is None) or _has_secret(primary)
188
+ if not eligible:
189
+ continue
190
+ router["providers"].append({
191
+ "id": pid,
192
+ "roles": _roles_for_cli(cli),
193
+ "enabled": True,
194
+ "secret_ref": primary or "",
195
+ "cli": cli,
196
+ })
197
+ discovered.append(pid)
198
+
199
+ out_dir = home / "Agentik_SSOT" / "providers"
200
+ out_dir.mkdir(parents=True, exist_ok=True)
201
+ target = out_dir / "router.yaml"
202
+ target.write_text(yaml.safe_dump(router, sort_keys=False, default_flow_style=False))
203
+ if discovered:
204
+ print(f"discovered providers from catalog: {discovered}")
205
+ else:
206
+ print("no providers discovered (no env vars / vault secrets set yet)")
207
+ print(f"wrote {target}")
208
+ return 0
209
+
210
+
103
211
  def cmd_autonomous(manifest: str, templates_dir: str, target_dir: str) -> int:
104
212
  data = _load_manifest(manifest)
105
213
  agents = data.get("autonomous_agents") or []
@@ -362,6 +470,8 @@ def main(argv: list[str]) -> int:
362
470
  rest = argv[2:]
363
471
  if cmd == "providers" and len(rest) == 2:
364
472
  return cmd_providers(*rest)
473
+ if cmd == "providers-from-catalog" and len(rest) == 1:
474
+ return cmd_providers_from_catalog(*rest)
365
475
  if cmd == "autonomous" and len(rest) == 3:
366
476
  return cmd_autonomous(*rest)
367
477
  if cmd == "rag" and len(rest) == 2:
@@ -14,7 +14,7 @@ step_preflight() {
14
14
  return 1
15
15
  fi
16
16
  have git || { err "git is required"; return 1; }
17
- [ "$OMEGA_PKG" = "unknown" ] && { err "no supported package manager (apt/dnf/brew)"; return 1; }
17
+ [ "$OMEGA_PKG" = "unknown" ] && { err "no supported package manager (apt/dnf/brew/pacman/apk/zypper)"; return 1; }
18
18
  mkdir -p "$STATE_DIR/logs"
19
19
  return 0
20
20
  }
@@ -26,15 +26,64 @@ step_system_deps() {
26
26
  # - fzf — primary picker for the new session manager (`omega`)
27
27
  # and required by the tmux-claude install script too
28
28
  # whiptail lives in `whiptail` (Debian/Ubuntu), `newt` (RHEL/Fedora),
29
- # `newt` (Homebrew). fzf is `fzf` on every platform.
29
+ # `libnewt` (Arch), `newt` (Alpine/SUSE), `newt` (Homebrew). fzf is `fzf`
30
+ # on every platform. nodejs+npm are required because 7 of 13 LLM CLIs
31
+ # (claude_code, gemini_cli, codex, opencode, qwen_code, continue_dev,
32
+ # gh_copilot) install via `npm -g`. Without npm at this step the LLM
33
+ # CLI installer (step 15) silently skips every Node-based CLI.
30
34
  local pkgs="python3 git tmux sqlite3 jq curl"
31
35
  case "$OMEGA_PKG" in
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 ;;
36
+ apt) sudo apt-get update -qq \
37
+ && sudo apt-get install -y -qq $pkgs whiptail fzf python3-venv python3-yaml nodejs npm ;;
38
+ dnf) sudo dnf install -y -q $pkgs newt fzf python3-pyyaml nodejs npm ;;
39
+ brew) brew install $pkgs newt fzf node 2>/dev/null || true ;;
40
+ pacman) sudo pacman -S --noconfirm --needed $pkgs libnewt fzf python-yaml nodejs npm ;;
41
+ apk) sudo apk add --no-cache $pkgs newt fzf py3-yaml nodejs npm ;;
42
+ zypper) sudo zypper --non-interactive install -y $pkgs newt fzf python3-PyYAML nodejs npm ;;
43
+ *) err "install manually: $pkgs (and whiptail/newt + fzf + nodejs/npm for the menu and LLM CLIs)"; return 1 ;;
36
44
  esac
37
45
 
46
+ # npm version check — 7 of 13 LLM CLIs (claude_code, gemini_cli, codex,
47
+ # opencode, qwen_code, continue_dev, gh_copilot) require npm >= 9 for
48
+ # the agentic CLI install to be reliable. Older npm still works for
49
+ # plain installs; we warn but never fail.
50
+ if have npm; then
51
+ local _npm_v _npm_major
52
+ _npm_v="$(npm --version 2>/dev/null || echo 0)"
53
+ _npm_major="$(printf '%s' "$_npm_v" | cut -d. -f1)"
54
+ case "$_npm_major" in
55
+ ''|*[!0-9]*) _npm_major=0 ;;
56
+ esac
57
+ if [ "$_npm_major" -lt 9 ]; then
58
+ info "warn: npm $_npm_v detected — LLM CLI install (step 15) recommends npm >= 9"
59
+ info " upgrade: npm install -g npm@latest (or use your OS package manager)"
60
+ fi
61
+ fi
62
+
63
+ # tmux version check — the bundled pro config uses allow-passthrough,
64
+ # buffer-limit, and the modern hook syntax which require tmux >= 3.3.
65
+ # Older tmux still works (omega itself only needs basic features), but
66
+ # the pro profile emits errors on those config lines at load time.
67
+ if have tmux; then
68
+ local _tmux_v _tmux_major _tmux_minor
69
+ _tmux_v="$(tmux -V 2>/dev/null | awk '{print $2}' | tr -d -c '0-9.')"
70
+ if [ -n "$_tmux_v" ]; then
71
+ _tmux_major="$(printf '%s' "$_tmux_v" | cut -d. -f1)"
72
+ _tmux_minor="$(printf '%s' "$_tmux_v" | cut -d. -f2 | tr -d -c '0-9')"
73
+ case "$_tmux_major" in
74
+ ''|*[!0-9]*) _tmux_major=0 ;;
75
+ esac
76
+ case "$_tmux_minor" in
77
+ ''|*[!0-9]*) _tmux_minor=0 ;;
78
+ esac
79
+ if [ "$_tmux_major" -lt 3 ] || { [ "$_tmux_major" = "3" ] && [ "$_tmux_minor" -lt 3 ]; }; then
80
+ info "warn: tmux $_tmux_v detected — pro config requires >= 3.3"
81
+ info " some bindings (allow-passthrough, buffer-limit) may error at load"
82
+ info " upgrade via your OS package manager ($OMEGA_PKG) and reinstall tmux"
83
+ fi
84
+ fi
85
+ fi
86
+
38
87
  # PyYAML — required by every install-side helper that reads the manifest
39
88
  # or audit YAMLs (bootstrap/lib/llm-clis.py, manifest-helpers.py, audit
40
89
  # generator). apt/dnf already deliver it via the system package above.
@@ -134,9 +183,12 @@ step_system_deps() {
134
183
  if ! have age; then
135
184
  info "installing age (powers the encrypted secrets vault)"
136
185
  case "$OMEGA_PKG" in
137
- apt) sudo apt-get install -y -qq age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
138
- dnf) sudo dnf install -y -q age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
139
- brew) brew install age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
186
+ apt) sudo apt-get install -y -qq age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
187
+ dnf) sudo dnf install -y -q age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
188
+ brew) brew install age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
189
+ pacman) sudo pacman -S --noconfirm --needed age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
190
+ apk) sudo apk add --no-cache age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
191
+ zypper) sudo zypper --non-interactive install -y age 2>>"$LOG_FILE" || info "age install failed (non-fatal)" ;;
140
192
  esac
141
193
  fi
142
194
  if ! have uv && [ ! -x "$HOME/.local/bin/uv" ]; then
@@ -204,7 +256,13 @@ step_llm_clis() {
204
256
  python3 "$helper" install "$MANIFEST" | sed 's/^/ /'
205
257
  return 0
206
258
  fi
207
- info "headless + no manifest llm_clis skipping (run omega upgrade later)"
259
+ # v0.19.43 --full mode (NONINTERACTIVE=1 + no MANIFEST) used to skip
260
+ # silently, leaving the operator with zero LLM CLIs after `npx ... --full`.
261
+ # Install the recommended set instead. install_cli is idempotent (skips
262
+ # already-installed CLIs), so this is safe on every entry.
263
+ info "headless --full mode: installing recommended LLM CLI set (claude_code + gemini_cli + opencode)"
264
+ python3 "$helper" install claude_code gemini_cli opencode | sed 's/^/ /' || \
265
+ warn "recommended LLM CLI install returned non-zero (non-fatal)"
208
266
  return 0
209
267
  fi
210
268
 
@@ -257,9 +315,30 @@ var/
257
315
  staging/
258
316
  EOF
259
317
  # deploy the repo's source blocks into the live tree
318
+ #
319
+ # v0.19.44 — explicit `/bin/cp -Rf` (not bare `cp`) for two reasons:
320
+ # 1. Bypass any `alias cp='cp -i'` on macOS — interactive cp fails
321
+ # SILENTLY when stdin is piped (the --full case via npx). The user
322
+ # hit this on v0.19.43: re-install showed success but cli.py was
323
+ # never refreshed → bare `omega` kept printing the v0.19.40 message.
324
+ # 2. `-f` forces overwrite even when the destination is read-only or
325
+ # a stale immutable bit lingers from a previous install.
260
326
  if [ -d "${OMEGA_REPO:-}/omega" ]; then
261
- cp -R "$OMEGA_REPO/omega/." "$OMEGA_HOME/"
262
- ok "deployed source blocks from $OMEGA_REPO/omega"
327
+ local prev_v=""
328
+ [ -f "$OMEGA_HOME/Agentik_SSOT/VERSION" ] \
329
+ && prev_v="$(cat "$OMEGA_HOME/Agentik_SSOT/VERSION" 2>/dev/null || echo '')"
330
+ /bin/cp -Rf "$OMEGA_REPO/omega/." "$OMEGA_HOME/" || {
331
+ err "cp -Rf failed deploying $OMEGA_REPO/omega → $OMEGA_HOME/"
332
+ return 1
333
+ }
334
+ local new_v=""
335
+ [ -f "$OMEGA_HOME/Agentik_SSOT/VERSION" ] \
336
+ && new_v="$(cat "$OMEGA_HOME/Agentik_SSOT/VERSION" 2>/dev/null || echo '')"
337
+ if [ -n "$prev_v" ] && [ "$prev_v" != "$new_v" ]; then
338
+ ok "code refreshed: ${prev_v} → ${new_v}"
339
+ else
340
+ ok "deployed source blocks from \$OMEGA_REPO/omega (v${new_v:-?})"
341
+ fi
263
342
  else
264
343
  err "repo source not found at \$OMEGA_REPO/omega"; return 1
265
344
  fi
@@ -351,7 +430,16 @@ step_engine() {
351
430
  local uv_bin; uv_bin="$(command -v uv || echo "$HOME/.local/bin/uv")"
352
431
  [ -x "$uv_bin" ] || { err "uv not found after step 10"; return 1; }
353
432
  cd "$OMEGA_HOME/Agentik_Engine" || { err "engine block missing"; return 1; }
354
- "$uv_bin" venv >/dev/null 2>&1 || { err "uv venv failed"; return 1; }
433
+ # Pin engine venv to Python 3.13 uv will download an Astral-managed
434
+ # build if the host's python3 is broken or unavailable. This sidesteps
435
+ # the macOS Tahoe brew Python 3.14 libexpat ABI mismatch and any other
436
+ # "system python3 silently inherited" failure mode. Fall back to a
437
+ # bare `uv venv` (uses whatever python3 is on PATH) so the install
438
+ # still succeeds on hosts where Python 3.13 cannot be procured.
439
+ if ! "$uv_bin" venv --python 3.13 >/dev/null 2>&1; then
440
+ info "engine venv: uv venv --python 3.13 failed, falling back to system python3"
441
+ "$uv_bin" venv >/dev/null 2>&1 || { err "uv venv failed"; return 1; }
442
+ fi
355
443
  "$uv_bin" pip install -e . >/dev/null 2>&1 || { err "engine install failed"; return 1; }
356
444
  mkdir -p "$OMEGA_HOME/Agentik_Tools/bin"
357
445
  ln -sf "$OMEGA_HOME/Agentik_Engine/.venv/bin/omega" "$OMEGA_HOME/Agentik_Tools/bin/omega"
@@ -387,9 +475,16 @@ step_tmux_config() {
387
475
  fi
388
476
  local marker="$HOME/.tmux/scripts/session-manager.sh"
389
477
  local installer_url="https://raw.githubusercontent.com/agentik-os/tmux-claude/main/install.sh"
478
+ # v0.19.43 — track whether ~/.tmux.conf is a tmux-claude install (either
479
+ # already present OR freshly installed by the curl pipe). When it is, we
480
+ # MUST also wire the Omega add-on (Alt+O bind) via
481
+ # install_into_home_tmux_conf — otherwise the user has tmux-claude but
482
+ # no way to reach the Omega menu from tmux.
483
+ local upstream_present=0
390
484
 
391
485
  if [ -f "$marker" ]; then
392
- info "tmux-claude already installed at $marker — skipping"
486
+ info "tmux-claude already installed at $marker — skipping curl"
487
+ upstream_present=1
393
488
  elif have curl; then
394
489
  info "installing canonical tmux-claude setup (agentik-os/tmux-claude)…"
395
490
  # We pipe through `bash -s -- --no-prompt` so the install doesn't ask
@@ -398,6 +493,7 @@ step_tmux_config() {
398
493
  if curl -fsSL "$installer_url" 2>>"$LOG_FILE" \
399
494
  | bash -s -- --no-prompt >>"$LOG_FILE" 2>&1; then
400
495
  ok "tmux-claude installed (~/.tmux/scripts/, ~/.tmux.conf)"
496
+ upstream_present=1
401
497
  else
402
498
  err "tmux-claude install failed — falling back to bundled pro profile"
403
499
  _tmux_fallback_pro_config
@@ -407,6 +503,28 @@ step_tmux_config() {
407
503
  _tmux_fallback_pro_config
408
504
  fi
409
505
 
506
+ # v0.19.43 — wire the Omega add-on AFTER an upstream tmux-claude install
507
+ # succeeds (or was already present). Without this step the upstream
508
+ # ~/.tmux.conf has NO `source-file` line for the OmegaOS add-on, so the
509
+ # Alt+O / Ctrl-b o binds never get sourced. install_into_home_tmux_conf
510
+ # detects the tmux-claude markers and falls into the
511
+ # preserved-tmux-claude branch — drops omega-tmux-add.conf and appends
512
+ # the source-file line at the end of ~/.tmux.conf.
513
+ # The fallback path already wires the add-on via _tmux_fallback_pro_config
514
+ # → install_into_home_tmux_conf, so we only need to do it here for the
515
+ # happy path.
516
+ if [ "$upstream_present" = "1" ]; then
517
+ PYTHONPATH="$OMEGA_HOME/Agentik_Engine" python3 - <<PY 2>>"$LOG_FILE" || warn "Omega tmux add-on wire failed (non-fatal)"
518
+ import os
519
+ os.environ["OMEGA_HOME"] = "$OMEGA_HOME"
520
+ from omega_engine.tmux import install_into_home_tmux_conf
521
+ result = install_into_home_tmux_conf(profile="pro")
522
+ print(f" Omega tmux wired: mode={result['mode']}")
523
+ print(f" written: {result['written']}")
524
+ print(f" bundled copy: {result['bundled_copy']}")
525
+ PY
526
+ fi
527
+
410
528
  # Always also drop the bundled config under $OMEGA_HOME so `omega tmux
411
529
  # install --profile pro` later remains a working manual recovery path.
412
530
  PYTHONPATH="$OMEGA_HOME/Agentik_Engine" python3 - <<PY 2>>"$LOG_FILE"
@@ -585,6 +703,15 @@ for entry in missing:
585
703
  elif pkg == "dnf":
586
704
  rc, out = _run(["sudo", "dnf", "install", "-y", "-q", install[pkg]])
587
705
  attempt = f"dnf install {install[pkg]}"
706
+ elif pkg == "pacman":
707
+ rc, out = _run(["sudo", "pacman", "-S", "--noconfirm", "--needed", install[pkg]])
708
+ attempt = f"pacman -S {install[pkg]}"
709
+ elif pkg == "apk":
710
+ rc, out = _run(["sudo", "apk", "add", "--no-cache", install[pkg]])
711
+ attempt = f"apk add {install[pkg]}"
712
+ elif pkg == "zypper":
713
+ rc, out = _run(["sudo", "zypper", "--non-interactive", "install", "-y", install[pkg]])
714
+ attempt = f"zypper install {install[pkg]}"
588
715
  elif "npm_global" in install and shutil.which("npm"):
589
716
  rc, out = _run(["npm", "install", "-g", "--silent", install["npm_global"]])
590
717
  attempt = f"npm install -g {install['npm_global']}"
@@ -1107,6 +1234,32 @@ _claude_plugins_prompt_checklist() {
1107
1234
  fi
1108
1235
  }
1109
1236
 
1237
+ # --- 47 -----------------------------------------------------------------------
1238
+ #
1239
+ # step_paperclip — register OmegaOS as the "omegaos" company with Paperclip.
1240
+ #
1241
+ # v0.19.43 — auto-runs at install time so the user doesn't have to type
1242
+ # `omega paperclip register` manually. Idempotent: if the 14 agents are
1243
+ # already registered, re-runs are a no-op content-equality check.
1244
+ #
1245
+ # This step does NOT install the Paperclip dashboard binary itself — that
1246
+ # remains opt-in via `omega tool install paperclipai`. It only writes the
1247
+ # company.json + per-agent JSON files at ~/.paperclip/companies/omegaos/
1248
+ # so Paperclip can SEE OmegaOS as soon as the operator starts the daemon.
1249
+ step_paperclip() {
1250
+ PYTHONPATH="$OMEGA_HOME/Agentik_Engine" python3 - <<PY 2>>"$LOG_FILE"
1251
+ import os
1252
+ os.environ["OMEGA_HOME"] = "$OMEGA_HOME"
1253
+ from omega_engine import paperclip_bridge as PB
1254
+ result = PB.register()
1255
+ print(f" Paperclip company registered: omegaos")
1256
+ print(f" agents written: {result.get('agents_written', 0)}")
1257
+ print(f" workspaces written: {result.get('workspaces_written', 0)}")
1258
+ print(f" paperclip_home: {result.get('paperclip_home', '?')}")
1259
+ PY
1260
+ return $?
1261
+ }
1262
+
1110
1263
  # --- 50 -----------------------------------------------------------------------
1111
1264
  step_telegram() {
1112
1265
  # Two distinct bots in the corrected v0.19.14 model:
@@ -1268,7 +1421,14 @@ step_accounts() {
1268
1421
  # we skip and the operator wires providers later via env or `omega account`.
1269
1422
  step_providers() {
1270
1423
  if [ -z "${MANIFEST:-}" ] || [ ! -f "$MANIFEST" ]; then
1271
- info "no manifest providers skipping (set env vars or run \`omega account login\` later)"
1424
+ # v0.19.43used to skip silently, leaving the runtime ModelRouter
1425
+ # with no router.yaml. Now auto-generate one from the live providers
1426
+ # catalog: any provider whose primary env var is set OR whose vault
1427
+ # secret exists is included. Oauth/local providers are always
1428
+ # included (no key needed).
1429
+ info "no manifest providers — auto-discovering from catalog"
1430
+ python3 "$OMEGA_REPO/bootstrap/lib/manifest-helpers.py" providers-from-catalog "$OMEGA_HOME" \
1431
+ | sed 's/^/ /'
1272
1432
  return 0
1273
1433
  fi
1274
1434
  python3 "$OMEGA_REPO/bootstrap/lib/manifest-helpers.py" providers "$MANIFEST" "$OMEGA_HOME" \
@@ -1337,17 +1497,22 @@ step_first_project() {
1337
1497
 
1338
1498
  # --- 59 -----------------------------------------------------------------------
1339
1499
  #
1340
- # step_hermes_session — guarantee one always-on Hermès chat session.
1500
+ # step_aisb_session — guarantee one always-on AISB master chat window.
1341
1501
  #
1342
- # Spawns the persistent `AISB-chat` tmux session running `omega aisb chat-loop`.
1343
- # This is the operator's terminal-side conversation entry point critical
1344
- # when Telegram isn't wired (minimal profile, or chat_id not yet set), and
1345
- # still useful when it IS wired (an always-attached local fallback that does
1346
- # not need internet).
1502
+ # v0.19.43 RENAMED from step_hermes_session (which was a misnomer: it
1503
+ # spawned the AISB master chat, not Hermès). v0.19.43 also moved the
1504
+ # chat from a standalone tmux session to a WINDOW inside the Omega
1505
+ # master session (Omega:aisb). spawn_aisb_chat now returns "Omega:aisb"
1506
+ # and spawns the Omega master if needed.
1347
1507
  #
1348
- # Idempotent: if `AISB-chat` is already alive, we just print attach
1508
+ # This is the operator's terminal-side conversation entry point
1509
+ # critical when Telegram isn't wired (minimal profile, or chat_id not
1510
+ # yet set), and still useful when it IS wired (an always-attached local
1511
+ # fallback that does not need internet).
1512
+ #
1513
+ # Idempotent: if the aisb window already exists, we just print attach
1349
1514
  # instructions.
1350
- step_hermes_session() {
1515
+ step_aisb_session() {
1351
1516
  if ! have tmux; then
1352
1517
  err "tmux not installed — step_system_deps should have caught this"
1353
1518
  return 1
@@ -1359,13 +1524,16 @@ step_hermes_session() {
1359
1524
  import os
1360
1525
  os.environ["OMEGA_HOME"] = "$OMEGA_HOME"
1361
1526
  from omega_engine import tmux
1362
- name = "AISB-chat"
1363
- if tmux.is_alive(name):
1364
- print(f" session already running: {name}")
1527
+ # v0.19.43 AISB master chat is now a WINDOW under the Omega master
1528
+ # session. omega_window_alive() checks the aisb window specifically;
1529
+ # attach_command() returns the right `tmux attach -t Omega:aisb` form.
1530
+ target = "Omega:aisb"
1531
+ if tmux.omega_window_alive("aisb"):
1532
+ print(f" AISB chat window already running: {target}")
1365
1533
  else:
1366
1534
  tmux.spawn_aisb_chat("$OMEGA_HOME")
1367
- print(f" spawned: {name} (always-on Hermès conversation)")
1368
- print(f" attach: {tmux.attach_command(name)}")
1535
+ print(f" spawned: {target} (always-on AISB master conversation)")
1536
+ print(f" attach: {tmux.attach_command(target)}")
1369
1537
  print(f" detach: Ctrl-b d (session keeps running)")
1370
1538
  PY
1371
1539
  return $?
@@ -1373,6 +1541,63 @@ PY
1373
1541
 
1374
1542
  # --- 60 -----------------------------------------------------------------------
1375
1543
  #
1544
+ # step_hermes_session — spawn the always-on Hermès chat window, IF the
1545
+ # vault key is present.
1546
+ #
1547
+ # v0.19.43 — separate step from step_aisb_session (which was previously
1548
+ # misnamed step_hermes_session but spawned AISB). Hermès has its own
1549
+ # credential isolation contract (ANTHROPIC_API_KEY_HERMES from the
1550
+ # vault — see omega_engine/hermes.py for the resolution chain). If the
1551
+ # vault key is missing we SKIP this step instead of falling back to Max
1552
+ # OAuth — operators who want Hermès running 24/7 must wire the key
1553
+ # explicitly. (Interactive `omega hermes` still falls through to Max
1554
+ # OAuth as a last resort so users can experiment without a paid key.)
1555
+ step_hermes_session() {
1556
+ if ! have tmux; then
1557
+ err "tmux not installed — step_system_deps should have caught this"
1558
+ return 1
1559
+ fi
1560
+ local omega="$OMEGA_HOME/Agentik_Tools/bin/omega"
1561
+ [ -x "$omega" ] || { err "omega CLI not found at $omega — step_engine incomplete"; return 1; }
1562
+
1563
+ local key_present=0
1564
+ if PYTHONPATH="$OMEGA_HOME/Agentik_Engine" python3 - <<PY 2>/dev/null
1565
+ from pathlib import Path
1566
+ import os, sys
1567
+ home = Path(os.environ.get("OMEGA_HOME", str(Path.home() / "Omega")))
1568
+ try:
1569
+ from omega_engine.vault import vault_read
1570
+ k = vault_read(home, "ANTHROPIC_API_KEY_HERMES")
1571
+ sys.exit(0 if (k and k.strip()) else 1)
1572
+ except Exception:
1573
+ sys.exit(1)
1574
+ PY
1575
+ then
1576
+ key_present=1
1577
+ fi
1578
+
1579
+ if [ "$key_present" = "1" ]; then
1580
+ PYTHONPATH="$OMEGA_HOME/Agentik_Engine" python3 - <<PY 2>>"$LOG_FILE"
1581
+ import os
1582
+ os.environ["OMEGA_HOME"] = "$OMEGA_HOME"
1583
+ from omega_engine import tmux
1584
+ target = "Omega:hermes"
1585
+ if tmux.omega_window_alive("hermes"):
1586
+ print(f" Hermès chat window already running: {target}")
1587
+ else:
1588
+ tmux.spawn_hermes_chat("$OMEGA_HOME")
1589
+ print(f" spawned: {target} (always-on Hermès conversation)")
1590
+ print(f" attach: {tmux.attach_command(target)}")
1591
+ PY
1592
+ else
1593
+ info " Hermès vault key (ANTHROPIC_API_KEY_HERMES) absent — skipping persistent session"
1594
+ info " wire it with: omega vault write ANTHROPIC_API_KEY_HERMES sk-ant-..."
1595
+ fi
1596
+ return 0
1597
+ }
1598
+
1599
+ # --- 65 -----------------------------------------------------------------------
1600
+ #
1376
1601
  # step_services — install the 24/7 service layer.
1377
1602
  #
1378
1603
  # Linux: write 3 systemd user units, `daemon-reload`, then `enable --now`.
@@ -38,6 +38,15 @@ v7.0 adds R-XX ownership and updated tooling without renaming any agent.
38
38
 
39
39
  ## Architecture
40
40
 
41
+ > **L0 Paperclip governance.** OmegaOS is registered as the "omegaos"
42
+ > company at `~/.paperclip/companies/omegaos/` with 14 agents (Hermès +
43
+ > 13 AISB). Each agent has a fixed reporting line (Hermès → top; Niobe
44
+ > → Hermès; Oracle/Construct/Morpheus/Architect → Oracle's chain).
45
+ > Agents should call `paperclip_bridge.send_heartbeat()` at meaningful
46
+ > lifecycle events (spawn / mission complete / blocked) so the
47
+ > Paperclip dashboard surfaces live status to the operator. This is
48
+ > additive — no AISB agent depends on Paperclip being running.
49
+
41
50
  ```
42
51
  USER (Telegram)
43
52
  |
@@ -274,6 +283,10 @@ Handoff templates: `protocols/handoff-templates.md`
274
283
  Shared protocol: `protocols/shared-protocol.md`
275
284
  LMC (Lead-Manager-Checker) protocol: `protocols/lmc-protocol.md` for SERAPH-grade audits.
276
285
 
286
+ **Fresh context handoff** — every brief dispatched downstream MUST be self-contained:
287
+ Mission / Purpose / Context / Done Criteria / Verify Command. The receiving agent
288
+ never sees this conversation; if it isn't written into the brief, it does not exist.
289
+
277
290
  ---
278
291
 
279
292
  ## AISB Nerve (v2.0, Omega-aware)