@agentikos/omega-os 0.19.34 → 0.19.36

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.
@@ -75,12 +75,45 @@ record_state_version() {
75
75
  }
76
76
 
77
77
  # run_step <name> <function>
78
+ #
79
+ # In NORMAL mode: prints `:: step <name>` headers + step output flows
80
+ # to the terminal in real time (the v0.19.34 behaviour).
81
+ #
82
+ # In FULL mode (INSTALL_FULL=1, set by `--full`): redirects each step's
83
+ # stdout+stderr to $LOG_FILE and updates a single progress bar line
84
+ # instead. No `:: step xx` chatter. Operator sees:
85
+ #
86
+ # Installing… ████████████░░░░░░░░░░ 14/21 66% 45-claude-plugins
87
+ #
88
+ # When the run finishes, the progress bar clears and the post-install
89
+ # card prints normally.
78
90
  run_step() {
79
91
  local name="$1" fn="$2"
80
92
  if step_done "$name" && [ "${FORCE:-0}" != "1" ]; then
93
+ if [ "${INSTALL_FULL:-0}" = "1" ]; then
94
+ _full_progress "$name" "skip"
95
+ return 0
96
+ fi
81
97
  log "${C_DIM}-- skip $name (already done)${C_RST}"
82
98
  return 0
83
99
  fi
100
+
101
+ if [ "${INSTALL_FULL:-0}" = "1" ]; then
102
+ # Redirect step output to the log; only the progress bar stays on screen.
103
+ _full_progress "$name" "run"
104
+ if "$fn" >>"$LOG_FILE" 2>&1; then
105
+ mark_done "$name"
106
+ _full_progress "$name" "ok"
107
+ else
108
+ # Clear the bar then surface the error inline.
109
+ printf "\r\033[K"
110
+ err "step $name failed — see $LOG_FILE"
111
+ die "step $name failed — fix the cause and re-run install.sh"
112
+ fi
113
+ return 0
114
+ fi
115
+
116
+ # Normal verbose mode (default).
84
117
  info "step $name"
85
118
  if "$fn"; then
86
119
  mark_done "$name"
@@ -90,6 +123,38 @@ run_step() {
90
123
  fi
91
124
  }
92
125
 
126
+ # _full_progress <step_name> <phase=run|ok|skip>
127
+ # Renders one line: "Installing… <bar> N/M % <current step>"
128
+ # Uses \r to overwrite — no scrollback noise.
129
+ _full_progress() {
130
+ local step="$1" phase="$2"
131
+ STEP_COUNT=$((${STEP_COUNT:-0} + 1))
132
+ local total="${STEP_TOTAL:-21}"
133
+ local current="$STEP_COUNT"
134
+ # Decrement displayed count for "skip" so the bar represents real progress
135
+ if [ "$phase" = "skip" ]; then
136
+ : # leave count as-is; skips still count as progress
137
+ fi
138
+ local pct=$((current * 100 / total))
139
+ local bar_width=30
140
+ local filled=$((current * bar_width / total))
141
+ local empty=$((bar_width - filled))
142
+ local bar=""
143
+ if [ "$filled" -gt 0 ]; then
144
+ bar="$(printf '█%.0s' $(seq 1 "$filled"))"
145
+ fi
146
+ if [ "$empty" -gt 0 ]; then
147
+ bar="${bar}$(printf '░%.0s' $(seq 1 "$empty"))"
148
+ fi
149
+ # \033[K clears to end of line so the previous step name doesn't bleed.
150
+ printf "\r ${C_ORANGE}Installing…${C_RST} ${bar} ${C_BOLD}%2d/%d${C_RST} %3d%% ${C_DIM}%-32s${C_RST}\033[K" \
151
+ "$current" "$total" "$pct" "$step"
152
+ # Final newline only when we hit the last step.
153
+ if [ "$current" -ge "$total" ]; then
154
+ printf "\n"
155
+ fi
156
+ }
157
+
93
158
  # --- user resolution ---------------------------------------------------------
94
159
  #
95
160
  # The installer is modular about WHICH user it runs as. Three modes, decided
package/install.sh CHANGED
@@ -25,6 +25,7 @@ source "$REPO_DIR/bootstrap/lib/steps.sh"
25
25
  PROFILE="vps"; MANIFEST=""; NONINTERACTIVE=0; FORCE=0
26
26
  USER_REQUESTED=""; CREATE_USER=0; CREATE_USER_NAME=""
27
27
  PROFILE_EXPLICIT=0; NO_PATH_SETUP=0; DRY_RUN=0
28
+ INSTALL_FULL=0; FULL_WANT_TELEGRAM=""
28
29
  [ "$(uname -s)" = "Darwin" ] && PROFILE="workstation"
29
30
 
30
31
  while [ $# -gt 0 ]; do
@@ -41,6 +42,14 @@ while [ $# -gt 0 ]; do
41
42
  --force) FORCE=1; shift ;;
42
43
  --no-path-setup) NO_PATH_SETUP=1; shift ;;
43
44
  --dry-run) DRY_RUN=1; shift ;;
45
+ # `--full` — silent full-recommended install. One prompt
46
+ # (Telegram yes/no), then progress bar only. Selects every
47
+ # recommended LLM CLI / MCP / Claude plugin / system CLI.
48
+ --full) INSTALL_FULL=1; NONINTERACTIVE=1; shift ;;
49
+ --full-no-telegram) INSTALL_FULL=1; NONINTERACTIVE=1
50
+ FULL_WANT_TELEGRAM=no; shift ;;
51
+ --full-with-telegram) INSTALL_FULL=1; NONINTERACTIVE=1
52
+ FULL_WANT_TELEGRAM=yes; shift ;;
44
53
  --user) USER_REQUESTED="${2:?}"; shift 2 ;;
45
54
  --create-user)
46
55
  CREATE_USER=1
@@ -123,15 +132,41 @@ fi
123
132
 
124
133
  # ───── UX header ─────
125
134
  install_banner
126
- preflight_card
135
+ # In --full mode skip the heavy preflight card; we'll show the progress
136
+ # bar instead. Banner still prints so the user sees "Omega OS vX.Y.Z".
137
+ if [ "${INSTALL_FULL:-0}" != "1" ]; then
138
+ preflight_card
139
+ fi
127
140
 
128
141
  # Interactive profile picker if the operator did not pass --profile.
129
- pick_profile_interactive
142
+ # (Skipped in --full mode — uses the default profile for the OS.)
143
+ if [ "${INSTALL_FULL:-0}" != "1" ]; then
144
+ pick_profile_interactive
145
+ fi
130
146
 
131
147
  # Final pre-run summary (one tight line so the operator knows what they
132
- # confirmed).
133
- log "${C_DIM}::${C_RST} starting install profile=${C_BOLD}$PROFILE${C_RST} os=$OMEGA_OS user=$OMEGA_USER home=$OMEGA_HOME"
134
- echo
148
+ # confirmed). Skipped in --full mode.
149
+ if [ "${INSTALL_FULL:-0}" != "1" ]; then
150
+ log "${C_DIM}::${C_RST} starting install — profile=${C_BOLD}$PROFILE${C_RST} os=$OMEGA_OS user=$OMEGA_USER home=$OMEGA_HOME"
151
+ echo
152
+ fi
153
+
154
+ # In --full mode we ask EXACTLY one question before anything else:
155
+ # "install Telegram?" If they pass --full-with-telegram or
156
+ # --full-no-telegram on the CLI, we skip even this prompt.
157
+ if [ "${INSTALL_FULL:-0}" = "1" ] && [ -z "${FULL_WANT_TELEGRAM:-}" ]; then
158
+ if [ -t 0 ] && [ "${NONINTERACTIVE:-0}" != "1" ]; then
159
+ if prompt_yes_no " Install the Telegram bot?" "no"; then
160
+ FULL_WANT_TELEGRAM=yes
161
+ else
162
+ FULL_WANT_TELEGRAM=no
163
+ fi
164
+ else
165
+ # Truly headless (piped stdin) — default to NO Telegram.
166
+ FULL_WANT_TELEGRAM=no
167
+ fi
168
+ echo
169
+ fi
135
170
 
136
171
  # Define the sequence ONCE so we can count it for the progress header.
137
172
  STEPS=(
@@ -157,7 +192,11 @@ STEPS=(
157
192
  "45-claude-plugins:step_claude_plugins"
158
193
  )
159
194
  if [ "$PROFILE" != "minimal" ]; then
160
- STEPS+=("50-telegram:step_telegram")
195
+ # In --full mode the Telegram step is gated by the single yes/no asked
196
+ # at the very top. Skip the step entirely when the user said no.
197
+ if [ "${INSTALL_FULL:-0}" != "1" ] || [ "${FULL_WANT_TELEGRAM:-}" != "no" ]; then
198
+ STEPS+=("50-telegram:step_telegram")
199
+ fi
161
200
  fi
162
201
  STEPS+=(
163
202
  "55-autonomous:step_autonomous"
@@ -170,25 +209,27 @@ STEPS+=(
170
209
  export STEP_TOTAL="${#STEPS[@]}"
171
210
  export STEP_COUNT=0
172
211
 
173
- # ───── Plan mode ─────
174
- # Show every step BEFORE running anything same shape as Claude Code's
175
- # plan mode: the operator sees the full sequence + can abort cleanly
176
- # with --dry-run, or just hit Enter to proceed.
177
- echo
178
- log "${C_DIM}─── Install plan (${STEP_TOTAL} steps, ~2-3 min total) ───${C_RST}"
179
- _idx=0
180
- for entry in "${STEPS[@]}"; do
181
- _idx=$((_idx + 1))
182
- _name="${entry%%:*}"
183
- log " ${C_DIM}${_idx}/${STEP_TOTAL}${C_RST} ${_name}"
184
- done
185
- log "${C_DIM}─────────────────────────────────────────${C_RST}"
186
- echo
212
+ # ───── Plan mode (verbose only) ─────
213
+ # In NORMAL mode: show every step BEFORE running + ask for confirmation.
214
+ # In --full mode: skip the plan operator already opted in via the flag.
215
+ if [ "${INSTALL_FULL:-0}" != "1" ]; then
216
+ echo
217
+ log "${C_DIM}─── Install plan (${STEP_TOTAL} steps, ~2-3 min total) ───${C_RST}"
218
+ _idx=0
219
+ for entry in "${STEPS[@]}"; do
220
+ _idx=$((_idx + 1))
221
+ _name="${entry%%:*}"
222
+ log " ${C_DIM}${_idx}/${STEP_TOTAL}${C_RST} ${_name}"
223
+ done
224
+ log "${C_DIM}─────────────────────────────────────────${C_RST}"
225
+ echo
226
+ fi
187
227
 
188
228
  if [ "${DRY_RUN:-0}" = "1" ]; then
189
229
  ok "dry-run — exiting without running any step"
190
230
  exit 0
191
231
  fi
232
+
192
233
  if [ "${NONINTERACTIVE:-0}" != "1" ] && [ -t 0 ]; then
193
234
  if ! prompt_yes_no "Proceed with the install?" "yes"; then
194
235
  log "aborted by operator — no changes made"
@@ -196,10 +237,22 @@ if [ "${NONINTERACTIVE:-0}" != "1" ] && [ -t 0 ]; then
196
237
  fi
197
238
  fi
198
239
 
240
+ # In --full mode, print the single header line that introduces the
241
+ # progress bar so the user knows what's happening.
242
+ if [ "${INSTALL_FULL:-0}" = "1" ]; then
243
+ echo
244
+ log " ${C_DIM}${STEP_TOTAL} steps · output piped to ${LOG_FILE} · Ctrl-c to abort${C_RST}"
245
+ echo
246
+ fi
247
+
199
248
  for entry in "${STEPS[@]}"; do
200
249
  name="${entry%%:*}"
201
250
  fn="${entry##*:}"
202
- step_progress_header "$name"
251
+ # The verbose per-step header is suppressed in --full mode (progress
252
+ # bar replaces it).
253
+ if [ "${INSTALL_FULL:-0}" != "1" ]; then
254
+ step_progress_header "$name"
255
+ fi
203
256
  run_step "$name" "$fn"
204
257
  done
205
258
 
@@ -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.34"
191
+ __version__ = "0.19.36"
192
192
 
193
193
  __all__ = [
194
194
  "__version__",
@@ -3147,28 +3147,14 @@ def cmd_menu_whiptail(_args: argparse.Namespace) -> int:
3147
3147
  return run_menu()
3148
3148
 
3149
3149
 
3150
- # Default LLM provider for chat sessions — recorded in a tiny state file
3151
- # so spawn_*_chat picks the right one. Hot-swap via `omega switch <id>`.
3152
- _PROVIDER_MARKER = "active-llm-provider"
3153
-
3154
-
3155
- def _provider_state_path() -> "Path":
3156
- from pathlib import Path
3157
- home = _omega_home()
3158
- return home / "Agentik_Extra" / "var" / _PROVIDER_MARKER
3159
-
3160
-
3161
- def _active_provider() -> str:
3162
- p = _provider_state_path()
3163
- if p.exists():
3164
- return p.read_text().strip() or "claude_code"
3165
- return "claude_code"
3166
-
3167
-
3168
- def _set_active_provider(provider_id: str) -> None:
3169
- p = _provider_state_path()
3170
- p.parent.mkdir(parents=True, exist_ok=True)
3171
- p.write_text(provider_id.strip() + "\n")
3150
+ # Active LLM provider now lives in omega_engine.provider_state so the
3151
+ # Textual TUI can read it without a circular cli↔tui import (v0.19.35 fix).
3152
+ from omega_engine.provider_state import (
3153
+ active_provider as _active_provider,
3154
+ set_active_provider as _set_active_provider,
3155
+ provider_state_path as _provider_state_path,
3156
+ PROVIDER_MARKER as _PROVIDER_MARKER,
3157
+ )
3172
3158
 
3173
3159
 
3174
3160
  def cmd_paperclip(args: argparse.Namespace) -> int:
@@ -0,0 +1,42 @@
1
+ """Active-LLM-provider marker — shared between cli.py and tui.py.
2
+
3
+ The provider state lives in a tiny file at
4
+ ``$OMEGA_HOME/Agentik_Extra/var/active-llm-provider``. `omega switch
5
+ <id>` writes here; the TUI and chat-spawn helpers read here.
6
+
7
+ Previously these helpers lived only in cli.py — but tui.py also needs
8
+ to read the active provider (to render `Switch LLM [current: claude_code]`
9
+ in the menu). Importing from cli would be circular (cli already imports
10
+ tui), so the helpers live here in their own neutral module.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from pathlib import Path
16
+
17
+
18
+ PROVIDER_MARKER = "active-llm-provider"
19
+ DEFAULT_PROVIDER = "claude_code"
20
+
21
+
22
+ def _omega_home() -> Path:
23
+ return Path(os.environ.get("OMEGA_HOME", str(Path.home() / "Omega")))
24
+
25
+
26
+ def provider_state_path() -> Path:
27
+ return _omega_home() / "Agentik_Extra" / "var" / PROVIDER_MARKER
28
+
29
+
30
+ def active_provider() -> str:
31
+ """Read the active provider id, falling back to claude_code."""
32
+ p = provider_state_path()
33
+ if p.exists():
34
+ return p.read_text().strip() or DEFAULT_PROVIDER
35
+ return DEFAULT_PROVIDER
36
+
37
+
38
+ def set_active_provider(provider_id: str) -> None:
39
+ """Persist the active provider id atomically."""
40
+ p = provider_state_path()
41
+ p.parent.mkdir(parents=True, exist_ok=True)
42
+ p.write_text(provider_id.strip() + "\n")
@@ -456,6 +456,12 @@ def _arrow_menu() -> int:
456
456
  def _section(name: str) -> str:
457
457
  return f"{ORANGE}{BOLD}── {name} ──{RST}"
458
458
 
459
+ # `_active_provider` lived in cli.py until v0.19.34 — calling it from
460
+ # here NameError'd at runtime because cli isn't imported here (would
461
+ # be circular: cli already imports tui). Now lives in a neutral
462
+ # `omega_engine.provider_state` module both can read.
463
+ from omega_engine.provider_state import active_provider as _active_provider
464
+
459
465
  def _build_items() -> list[tuple[str, str]]:
460
466
  """Return (display, action_key) — section headers have key '__sep__'."""
461
467
  provider = _active_provider()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omega-engine"
3
- version = "0.19.34"
3
+ version = "0.19.36"
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"
@@ -0,0 +1,104 @@
1
+ """Regression tests for omega_engine.tui — specifically the NameError
2
+ that escaped pure-import tests in v0.19.34.
3
+
4
+ v0.19.34's `_arrow_menu` referenced `_active_provider` (defined only in
5
+ cli.py) which failed with NameError when actually invoked, even though
6
+ `import omega_engine.tui` succeeded. Lesson: import-only smoke tests
7
+ don't catch undefined-name errors inside function bodies. These tests
8
+ exercise the function bodies enough to catch the same class of bug.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import inspect
13
+ import os
14
+ import tempfile
15
+ import unittest
16
+ from pathlib import Path
17
+
18
+
19
+ class TestTuiBodyResolves(unittest.TestCase):
20
+ """Compile + exec the bodies of TUI functions to surface undefined
21
+ names. Pytest's normal collection only imports modules — it never
22
+ runs `_arrow_menu` itself."""
23
+
24
+ def test_arrow_menu_body_compiles(self):
25
+ from omega_engine.tui import _arrow_menu
26
+ src = inspect.getsource(_arrow_menu)
27
+ # If a referenced helper isn't importable from inside the
28
+ # function, this raises (catches v0.19.34's NameError class).
29
+ compile(src, "<test>", "exec")
30
+
31
+ def test_plain_repl_body_compiles(self):
32
+ from omega_engine.tui import _plain_repl
33
+ src = inspect.getsource(_plain_repl)
34
+ compile(src, "<test>", "exec")
35
+
36
+ def test_dispatch_slash_for_parse_only_commands(self):
37
+ """Every documented slash command that doesn't shell out to the
38
+ omega binary must produce a CommandResult (no NameError, no
39
+ AttributeError). Commands that shell out (`/audit`, `/switch`,
40
+ `/doctor`, etc.) need the omega binary on disk — those are
41
+ smoke-tested separately on real installs."""
42
+ from omega_engine.tui import dispatch_slash
43
+ # These never shell out — pure dispatch.
44
+ cmds = [
45
+ "/help", "/quit", "/detach", "/unknown_xyz_command",
46
+ "/audit", # no args → static text only, no shell-out
47
+ ]
48
+ for c in cmds:
49
+ r = dispatch_slash(c)
50
+ self.assertIsNotNone(r, f"dispatch_slash({c!r}) returned None")
51
+ self.assertTrue(
52
+ r.text or r.exit_tui or r.detach,
53
+ f"dispatch_slash({c!r}) returned empty result",
54
+ )
55
+
56
+
57
+ class TestProviderStateSharedModule(unittest.TestCase):
58
+ """The fix for v0.19.34's NameError was to extract the provider
59
+ state helpers into omega_engine.provider_state. Lock that in."""
60
+
61
+ def test_provider_state_module_exposes_helpers(self):
62
+ from omega_engine import provider_state as PS
63
+ for name in (
64
+ "active_provider", "set_active_provider",
65
+ "provider_state_path", "PROVIDER_MARKER", "DEFAULT_PROVIDER",
66
+ ):
67
+ self.assertTrue(hasattr(PS, name),
68
+ f"provider_state.{name} missing — TUI + CLI rely on it")
69
+
70
+ def test_get_set_roundtrip(self):
71
+ from omega_engine.provider_state import (
72
+ active_provider, set_active_provider,
73
+ )
74
+ with tempfile.TemporaryDirectory() as tmp:
75
+ old_home = os.environ.get("OMEGA_HOME")
76
+ os.environ["OMEGA_HOME"] = tmp
77
+ try:
78
+ # Default before any set
79
+ self.assertEqual(active_provider(), "claude_code")
80
+ # Set + read
81
+ set_active_provider("gemini_cli")
82
+ self.assertEqual(active_provider(), "gemini_cli")
83
+ # Whitespace stripped
84
+ set_active_provider(" codex ")
85
+ self.assertEqual(active_provider(), "codex")
86
+ finally:
87
+ if old_home is None:
88
+ os.environ.pop("OMEGA_HOME", None)
89
+ else:
90
+ os.environ["OMEGA_HOME"] = old_home
91
+
92
+ def test_cli_imports_from_provider_state(self):
93
+ """The cli.py local `_active_provider` must be re-exported from
94
+ provider_state — not a divergent definition (the bug was that
95
+ cli.py owned the definition and tui.py couldn't see it)."""
96
+ from omega_engine import cli, provider_state
97
+ self.assertIs(cli._active_provider, provider_state.active_provider,
98
+ "cli._active_provider must alias provider_state.active_provider")
99
+ self.assertIs(cli._set_active_provider,
100
+ provider_state.set_active_provider)
101
+
102
+
103
+ if __name__ == "__main__":
104
+ unittest.main()
@@ -1 +1 @@
1
- 0.19.34
1
+ 0.19.36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentikos/omega-os",
3
- "version": "0.19.34",
3
+ "version": "0.19.36",
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"