@agentikos/omega-os 0.19.12 → 0.19.14

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 (23) hide show
  1. package/bootstrap/lib/steps.sh +80 -18
  2. package/omega/Agentik_Engine/omega_engine/__init__.py +1 -1
  3. package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
  4. package/omega/Agentik_Engine/omega_engine/__pycache__/aisb_chat.cpython-313.pyc +0 -0
  5. package/omega/Agentik_Engine/omega_engine/__pycache__/hermes.cpython-313.pyc +0 -0
  6. package/omega/Agentik_Engine/omega_engine/aisb_chat.py +43 -0
  7. package/omega/Agentik_Engine/omega_engine/daemons/__pycache__/telegram.cpython-313.pyc +0 -0
  8. package/omega/Agentik_Engine/omega_engine/daemons/telegram.py +27 -14
  9. package/omega/Agentik_Engine/omega_engine/genesis/__pycache__/stack.cpython-313.pyc +0 -0
  10. package/omega/Agentik_Engine/omega_engine/genesis/stack.py +30 -0
  11. package/omega/Agentik_Engine/omega_engine/hermes.py +34 -8
  12. package/omega/Agentik_Engine/pyproject.toml +1 -1
  13. package/omega/Agentik_Engine/tests/__pycache__/test_hermes_and_ua.cpython-313-pytest-8.4.2.pyc +0 -0
  14. package/omega/Agentik_Engine/tests/__pycache__/test_hermes_and_ua.cpython-313.pyc +0 -0
  15. package/omega/Agentik_Engine/tests/__pycache__/test_telegram_history.cpython-313-pytest-8.4.2.pyc +0 -0
  16. package/omega/Agentik_Engine/tests/__pycache__/test_telegram_history.cpython-313.pyc +0 -0
  17. package/omega/Agentik_Engine/tests/test_hermes_and_ua.py +56 -8
  18. package/omega/Agentik_Engine/tests/test_telegram_history.py +63 -15
  19. package/omega/Agentik_SSOT/VERSION +1 -1
  20. package/omega/Agentik_SSOT/docs/LAYERS.md +130 -73
  21. package/omega/Agentik_SSOT/docs/references/README.md +17 -0
  22. package/omega/Agentik_SSOT/skills/desktop-architecture/SKILL.md +61 -0
  23. package/package.json +1 -1
@@ -865,37 +865,55 @@ _claude_plugins_prompt_checklist() {
865
865
 
866
866
  # --- 50 -----------------------------------------------------------------------
867
867
  step_telegram() {
868
- local token chat_id
869
- ask token "Telegram bot token (from @BotFather)" "${TELEGRAM_TOKEN:-}"
870
- if [ -z "$token" ]; then
871
- err "a Telegram bot token is required (or install with --profile minimal)"
868
+ # Two distinct bots in the corrected v0.19.14 model:
869
+ # * AISB bot (token A) REQUIRED. Runs missions on Max OAuth.
870
+ # * Hermès bot (token H) — OPTIONAL. Meta-companion on Anthropic API.
871
+ # And one optional API key (ANTHROPIC_API_KEY_HERMES) gating Hermès AI.
872
+ # Old `TELEGRAM_TOKEN` is treated as the AISB token (back-compat).
873
+ local aisb_token hermes_token anthropic_key chat_id
874
+
875
+ # 1. AISB bot — required.
876
+ info "AISB bot — required. Talks to OmegaOS on Claude Max OAuth."
877
+ ask aisb_token "AISB Telegram bot token (from @BotFather)" "${TELEGRAM_TOKEN_AISB:-${TELEGRAM_TOKEN:-}}"
878
+ if [ -z "$aisb_token" ]; then
879
+ err "an AISB bot token is required (or install with --profile minimal)"
872
880
  return 1
873
881
  fi
874
882
  if have curl; then
875
- if ! curl -s "https://api.telegram.org/bot${token}/getMe" | grep -q '"ok":true'; then
876
- err "Telegram token rejected by the API"
883
+ if ! curl -s "https://api.telegram.org/bot${aisb_token}/getMe" | grep -q '"ok":true'; then
884
+ err "AISB Telegram token rejected by the API"
877
885
  return 1
878
886
  fi
879
887
  fi
888
+
880
889
  local sec="$OMEGA_HOME/Agentik_Extra/etc/secrets"
881
890
  mkdir -p "$sec"
882
- printf 'TELEGRAM_TOKEN=%s\n' "$token" > "$sec/telegram.env"
891
+ # Write a single .env file consumed by both daemons; vault still holds
892
+ # the canonical encrypted copy.
893
+ {
894
+ echo "# Generated by OmegaOS install — do not edit by hand."
895
+ echo "# AISB bot (OmegaOS-native, Claude Max OAuth)"
896
+ printf 'TELEGRAM_TOKEN_AISB=%s\n' "$aisb_token"
897
+ printf 'TELEGRAM_TOKEN=%s\n' "$aisb_token" # back-compat alias
898
+ } > "$sec/telegram.env"
883
899
  chmod 600 "$sec/telegram.env"
884
- # Also write the token into the encrypted vault for the new code path.
885
- python3 - "$OMEGA_HOME" "$token" <<'PY' || true
900
+ python3 - "$OMEGA_HOME" "$aisb_token" <<'PY' || true
886
901
  import sys
887
902
  from pathlib import Path
888
903
  home = Path(sys.argv[1])
889
904
  sys.path.insert(0, str(home / "Agentik_Engine"))
890
905
  try:
891
906
  from omega_engine.vault import vault_write
907
+ # Write under BOTH names so old code paths reading TELEGRAM_TOKEN keep
908
+ # working without a separate migration step.
909
+ vault_write(home, "TELEGRAM_TOKEN_AISB", sys.argv[2])
892
910
  vault_write(home, "TELEGRAM_TOKEN", sys.argv[2])
893
911
  except Exception as exc:
894
912
  print(f"(vault write skipped: {exc})")
895
913
  PY
896
- ok "Telegram bot validated and wired"
914
+ ok "AISB Telegram bot validated and wired"
897
915
 
898
- # Capture the forum group chat id — manifest first, then prompt.
916
+ # 2. AISB group chat id — same as before (scopes missions to a group).
899
917
  chat_id="$(python3 - "$MANIFEST" <<'PY' 2>/dev/null || true
900
918
  import sys, yaml
901
919
  try:
@@ -906,14 +924,11 @@ except Exception:
906
924
  print('')
907
925
  PY
908
926
  )"
909
- chat_id="${chat_id//$'\n'/}"
927
+ chat_id="$(printf '%s' "$chat_id" | tr -d '\n ')"
910
928
  if [ -z "$chat_id" ]; then
911
- ask chat_id "Telegram forum group chat id (e.g. -1001234567890; blank to set later)" ""
929
+ ask chat_id "AISB forum group chat id (e.g. -1001234567890; blank to set later)" ""
912
930
  fi
913
931
  if [ -n "$chat_id" ]; then
914
- python3 "$OMEGA_REPO/bootstrap/lib/manifest-helpers.py" telegram-chat-id \
915
- "$MANIFEST" "$OMEGA_HOME" 2>/dev/null | sed 's/^/ /' || true
916
- # When chat_id came from the prompt (not the manifest), persist directly.
917
932
  python3 - "$OMEGA_HOME" "$chat_id" <<'PY' || true
918
933
  import sys
919
934
  from pathlib import Path
@@ -922,12 +937,59 @@ sys.path.insert(0, str(home / "Agentik_Engine"))
922
937
  try:
923
938
  from omega_engine.vault import vault_write
924
939
  vault_write(home, "TELEGRAM_GROUP_ID", sys.argv[2])
925
- print(f"Telegram group_chat_id stored: {sys.argv[2]}")
940
+ print(f"AISB group chat_id stored: {sys.argv[2]}")
926
941
  except Exception as exc:
927
942
  print(f"(vault write skipped: {exc})")
928
943
  PY
929
944
  else
930
- info "no Telegram group_chat_id captured — set later with \`omega telegram set-group <id>\`"
945
+ info "no AISB group_chat_id captured — set later with \`omega telegram set-group <id>\`"
946
+ fi
947
+
948
+ # 3. Hermès bot — OPTIONAL. Skip cleanly if the operator just hits Enter.
949
+ echo
950
+ info "Hermès bot — OPTIONAL. Meta-companion on Anthropic API (you pay per call)."
951
+ info " Skip both prompts if you don't want Hermès yet — OmegaOS works fully without it."
952
+ ask hermes_token "Hermès Telegram bot token (blank = skip Hermès)" "${TELEGRAM_TOKEN_HERMES:-}"
953
+ if [ -n "$hermes_token" ]; then
954
+ if have curl; then
955
+ if ! curl -s "https://api.telegram.org/bot${hermes_token}/getMe" | grep -q '"ok":true'; then
956
+ err "Hermès Telegram token rejected by the API — continuing without Hermès"
957
+ hermes_token=""
958
+ fi
959
+ fi
960
+ fi
961
+ if [ -n "$hermes_token" ]; then
962
+ ask anthropic_key "Anthropic API key for Hermès (sk-ant-...; blank = set later)" \
963
+ "${ANTHROPIC_API_KEY_HERMES:-${ANTHROPIC_API_KEY:-}}"
964
+ {
965
+ echo "# Hermès bot (Layer 2 meta-companion, Anthropic API)"
966
+ printf 'TELEGRAM_TOKEN_HERMES=%s\n' "$hermes_token"
967
+ } >> "$sec/telegram.env"
968
+ if [ -n "$anthropic_key" ]; then
969
+ printf 'ANTHROPIC_API_KEY_HERMES=%s\n' "$anthropic_key" >> "$sec/telegram.env"
970
+ fi
971
+ chmod 600 "$sec/telegram.env"
972
+ python3 - "$OMEGA_HOME" "$hermes_token" "$anthropic_key" <<'PY' || true
973
+ import sys
974
+ from pathlib import Path
975
+ home = Path(sys.argv[1])
976
+ sys.path.insert(0, str(home / "Agentik_Engine"))
977
+ try:
978
+ from omega_engine.vault import vault_write
979
+ vault_write(home, "TELEGRAM_TOKEN_HERMES", sys.argv[2])
980
+ if sys.argv[3]:
981
+ vault_write(home, "ANTHROPIC_API_KEY_HERMES", sys.argv[3])
982
+ except Exception as exc:
983
+ print(f"(vault write skipped: {exc})")
984
+ PY
985
+ if [ -n "$anthropic_key" ]; then
986
+ ok "Hermès Telegram bot + Anthropic key wired"
987
+ else
988
+ info "Hermès Telegram bot wired (no Anthropic key yet — set with"
989
+ info " omega vault write ANTHROPIC_API_KEY_HERMES <sk-ant-...>)"
990
+ fi
991
+ else
992
+ info "Hermès skipped — wire later with \`omega telegram setup-hermes\`"
931
993
  fi
932
994
  return 0
933
995
  }
@@ -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.12"
191
+ __version__ = "0.19.14"
192
192
 
193
193
  __all__ = [
194
194
  "__version__",
@@ -60,6 +60,49 @@ def _clear_history(home: Path) -> int:
60
60
  conn.close()
61
61
 
62
62
 
63
+ def chat_once(home: Path, text: str, *,
64
+ topic_id: int = None,
65
+ record_history: bool = True) -> str:
66
+ """Run ONE conversation turn against the AISB envelope and return the
67
+ reply. Stateless from the caller's POV — history is persisted via
68
+ ``telegram_history`` keyed on ``topic_id`` (a single global default
69
+ for the tmux REPL; chat_id for Telegram DMs to keep per-user threads).
70
+
71
+ Extracted so the Telegram DM path can talk to the same agent the
72
+ tmux REPL talks to, without duplicating env+envelope+provider plumbing.
73
+ """
74
+ import time
75
+ from omega_engine.envelope import EnvelopeContext, build_envelope
76
+ from omega_engine.provider import AgentRequest
77
+ from omega_engine.router import ModelRouter
78
+ from omega_engine.telegram_history import (
79
+ build_context_prompt, record_inbound, record_outbound,
80
+ )
81
+ if topic_id is None:
82
+ topic_id = CHAT_TOPIC
83
+ if record_history:
84
+ record_inbound(home, topic_id=topic_id, text=text)
85
+ enriched = build_context_prompt(
86
+ home, topic_id=topic_id, new_intent=text, limit=10,
87
+ )
88
+ ctx = EnvelopeContext(
89
+ role="aisb",
90
+ intent=enriched,
91
+ task_id=f"chat-{int(time.time())}",
92
+ )
93
+ env = build_envelope(home, ctx)
94
+ router = ModelRouter.auto()
95
+ provider = router.resolve("aisb")
96
+ result = provider.run(AgentRequest(
97
+ role="aisb", prompt=env["user"], context={},
98
+ system=env["system"],
99
+ ))
100
+ reply = (result.text or "").strip() or "(empty reply)"
101
+ if record_history:
102
+ record_outbound(home, topic_id=topic_id, text=reply)
103
+ return reply
104
+
105
+
63
106
  def run_chat_loop() -> int:
64
107
  """Block until /quit. Returns 0 on clean exit."""
65
108
  home = _omega_home()
@@ -153,33 +153,46 @@ def _route_one(
153
153
  """
154
154
  from omega_engine import telegram_history
155
155
 
156
- # DM (no topic) — log it, refuse to start a mission. AISB DM
157
- # is NOT where the orchestration runs; it's where the operator
158
- # talks to the bot 1:1. The mission must run inside a project topic.
156
+ # DM (no topic) — route to the AISB conversational agent (same one
157
+ # the tmux REPL talks to). This used to refuse with "Missions only
158
+ # run from a project topic" which broke the most basic UX (a fresh
159
+ # install has no group topic yet, so the bot was effectively dead).
160
+ #
161
+ # Per-chat conversation history is namespaced by chat_id (encoded
162
+ # as a negative topic_id to avoid collisions with real Telegram
163
+ # forum topics, which are always positive small integers).
159
164
  if topic_id is None:
160
- # We still record DM history so the operator's question + the
161
- # bot's eventual reply (handled elsewhere) are linked.
165
+ dm_topic = -abs(int(chat_id)) if chat_id is not None else 0
162
166
  telegram_history.record_inbound(
163
- home, topic_id=0, text=text, message_id=message_id,
167
+ home, topic_id=dm_topic, text=text, message_id=message_id,
164
168
  )
165
169
  logger.info(
166
- "telegram in: DM (chat=%s) text=%r — refusing to start a "
167
- "mission from the DM; ask in a project topic instead",
170
+ "telegram in: DM (chat=%s) text=%r — routing to AISB chat",
168
171
  chat_id, text[:80],
169
172
  )
173
+ try:
174
+ from omega_engine.aisb_chat import chat_once
175
+ reply = chat_once(home, text, topic_id=dm_topic,
176
+ record_history=False)
177
+ except Exception as exc: # noqa: BLE001
178
+ logger.warning("telegram DM: chat_once failed",
179
+ exc_info=True)
180
+ reply = (
181
+ f"I hit an error talking to the AISB agent: {exc}\n"
182
+ "Check `omega doctor` for provider/account status."
183
+ )
184
+ telegram_history.record_outbound(
185
+ home, topic_id=dm_topic, text=reply,
186
+ )
170
187
  if telegram_bridge is not None and chat_id is not None:
171
188
  try:
172
189
  telegram_bridge._call("sendMessage", { # noqa: SLF001
173
190
  "chat_id": str(chat_id),
174
- "text": (
175
- "Missions only run from a project topic in the "
176
- "OmegaOS group — not the DM. Open the topic for "
177
- "the project and re-send your request there."
178
- ),
191
+ "text": reply[:3900],
179
192
  })
180
193
  except Exception: # noqa: BLE001
181
194
  logger.warning("telegram daemon: DM reply failed", exc_info=True)
182
- return "dm:refused"
195
+ return "dm:aisb-chat"
183
196
 
184
197
  # Topic message — record + route.
185
198
  telegram_history.record_inbound(
@@ -176,6 +176,35 @@ def _backend_supabase_stack() -> Stack:
176
176
  )
177
177
 
178
178
 
179
+ def _claude_dashboard_stack() -> Stack:
180
+ """The Claude Code-grade premium dashboard stack.
181
+
182
+ Picks up the architecture contract from
183
+ ``Agentik_SSOT/docs/references/CLAUDE-DESKTOP-FRONTEND-ARCHITECTURE.md``
184
+ during PRD + branding + design-system phases. Worker prompts include
185
+ the `/desktop-architecture` skill so every generated component traces
186
+ back to the same reference doc.
187
+ """
188
+ return Stack(
189
+ id="claude-dashboard",
190
+ title="Claude-grade premium dashboard (Next.js + Convex + shadcn + Motion + references doc)",
191
+ config=StackConfig(
192
+ type="web",
193
+ frontend="nextjs",
194
+ backend="convex",
195
+ deploy="vercel",
196
+ payments="stripe",
197
+ ui_kit="shadcn",
198
+ auth="clerk",
199
+ extras=["desktop-architecture-reference"],
200
+ ),
201
+ why="When the operator wants the Claude / Linear / Vercel / Stripe / "
202
+ "Elevenlabs dashboard look. Genesis phases pull from "
203
+ "Agentik_SSOT/docs/references/CLAUDE-DESKTOP-FRONTEND-ARCHITECTURE.md "
204
+ "as the single source of design truth.",
205
+ )
206
+
207
+
179
208
  CANONICAL_STACKS: dict[str, Stack] = {
180
209
  s.id: s for s in (
181
210
  default_stack(),
@@ -183,6 +212,7 @@ CANONICAL_STACKS: dict[str, Stack] = {
183
212
  _mobile_cross_stack(),
184
213
  _desktop_tauri_stack(),
185
214
  _backend_supabase_stack(),
215
+ _claude_dashboard_stack(),
186
216
  )
187
217
  }
188
218
 
@@ -248,19 +248,45 @@ def build_env(
248
248
  *,
249
249
  extra: dict[str, str] | None = None,
250
250
  creds_path: str | Path | None = None,
251
+ omega_home: str | Path | None = None,
251
252
  ) -> dict[str, str]:
252
253
  """Build the env dict for every Hermes invocation.
253
254
 
254
- Injects ``CLAUDE_CODE_OAUTH_TOKEN`` from the live credentials file +
255
- a few sane defaults (``HERMES_PROVIDER=anthropic``,
256
- ``HERMES_MODEL=claude-opus-4-7``). Caller env wins via ``extra``.
255
+ v0.19.14 Hermès now defaults to its OWN Anthropic API key (budget
256
+ isolation from the OmegaOS Claude Max OAuth):
257
+
258
+ 1. ``ANTHROPIC_API_KEY_HERMES`` from the encrypted vault → primary.
259
+ 2. ``ANTHROPIC_API_KEY`` already in the shell env → fallback.
260
+ 3. ``CLAUDE_CODE_OAUTH_TOKEN`` from ``~/.claude/.credentials.json``
261
+ → LAST RESORT only, with a clear warning that this routes Hermès
262
+ calls through your Max sub.
263
+
264
+ Caller env wins via ``extra``.
257
265
  """
258
- token = claude_oauth_token(creds_path)
259
266
  env = {**os.environ}
260
- env["CLAUDE_CODE_OAUTH_TOKEN"] = token
261
- # If the operator had stale anthropic env vars set, the OAuth token
262
- # takes precedence. We do NOT clear them — operators may have legit
263
- # API key setups for other tools.
267
+ # 1. Vault first.
268
+ key = ""
269
+ try:
270
+ from omega_engine.vault import vault_read
271
+ from pathlib import Path as _P
272
+ home = _P(omega_home) if omega_home else _P(
273
+ os.environ.get("OMEGA_HOME", str(_P.home() / "Omega"))
274
+ )
275
+ key = (vault_read(home, "ANTHROPIC_API_KEY_HERMES") or "").strip()
276
+ except Exception: # noqa: BLE001
277
+ key = ""
278
+ # 2. Shell env fallback.
279
+ if not key:
280
+ key = (os.environ.get("ANTHROPIC_API_KEY") or "").strip()
281
+ # 3. Last-resort OAuth (preserves the old behaviour without making
282
+ # Hermès depend on it).
283
+ if not key:
284
+ try:
285
+ env["CLAUDE_CODE_OAUTH_TOKEN"] = claude_oauth_token(creds_path)
286
+ except Exception: # noqa: BLE001
287
+ pass
288
+ if key:
289
+ env["ANTHROPIC_API_KEY"] = key
264
290
  env.setdefault("HERMES_PROVIDER", "anthropic")
265
291
  env.setdefault("HERMES_MODEL", "claude-opus-4-7")
266
292
  if extra:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omega-engine"
3
- version = "0.19.12"
3
+ version = "0.19.14"
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"
@@ -95,13 +95,43 @@ class TestClaudeOAuthRead(unittest.TestCase):
95
95
 
96
96
 
97
97
  class TestBuildEnv(unittest.TestCase):
98
- def test_env_includes_oauth_token_and_defaults(self):
98
+ """v0.19.14 — Hermès prefers ANTHROPIC_API_KEY_HERMES from the vault,
99
+ falls back to ANTHROPIC_API_KEY env, then last-resort to the Max
100
+ OAuth at ``~/.claude/.credentials.json``."""
101
+
102
+ def test_env_uses_anthropic_key_env_when_set(self):
103
+ with tempfile.TemporaryDirectory() as tmp:
104
+ p = _fake_creds(Path(tmp) / "c.json", token="tk-Z")
105
+ old = os.environ.get("ANTHROPIC_API_KEY")
106
+ os.environ["ANTHROPIC_API_KEY"] = "sk-ant-test"
107
+ os.environ.pop("OMEGA_HOME", None)
108
+ try:
109
+ env = H.build_env(creds_path=p)
110
+ self.assertEqual(env.get("ANTHROPIC_API_KEY"), "sk-ant-test",
111
+ "Hermès must prefer ANTHROPIC_API_KEY over OAuth")
112
+ # OAuth NOT exposed when an Anthropic key is available.
113
+ self.assertNotIn("CLAUDE_CODE_OAUTH_TOKEN", env)
114
+ finally:
115
+ if old is None:
116
+ os.environ.pop("ANTHROPIC_API_KEY", None)
117
+ else:
118
+ os.environ["ANTHROPIC_API_KEY"] = old
119
+
120
+ def test_env_falls_back_to_oauth_when_no_anthropic_key(self):
99
121
  with tempfile.TemporaryDirectory() as tmp:
100
122
  p = _fake_creds(Path(tmp) / "c.json", token="tk-Z")
101
- env = H.build_env(creds_path=p)
102
- self.assertEqual(env["CLAUDE_CODE_OAUTH_TOKEN"], "tk-Z")
103
- self.assertEqual(env["HERMES_PROVIDER"], "anthropic")
104
- self.assertEqual(env["HERMES_MODEL"], "claude-opus-4-7")
123
+ old = os.environ.get("ANTHROPIC_API_KEY")
124
+ os.environ.pop("ANTHROPIC_API_KEY", None)
125
+ os.environ["OMEGA_HOME"] = tmp # no vault key here
126
+ try:
127
+ env = H.build_env(creds_path=p)
128
+ self.assertEqual(env.get("CLAUDE_CODE_OAUTH_TOKEN"), "tk-Z")
129
+ self.assertEqual(env["HERMES_PROVIDER"], "anthropic")
130
+ self.assertEqual(env["HERMES_MODEL"], "claude-opus-4-7")
131
+ finally:
132
+ os.environ.pop("OMEGA_HOME", None)
133
+ if old is not None:
134
+ os.environ["ANTHROPIC_API_KEY"] = old
105
135
 
106
136
  def test_env_extra_overrides_default(self):
107
137
  with tempfile.TemporaryDirectory() as tmp:
@@ -110,9 +140,27 @@ class TestBuildEnv(unittest.TestCase):
110
140
  extra={"HERMES_MODEL": "claude-sonnet-4-7"})
111
141
  self.assertEqual(env["HERMES_MODEL"], "claude-sonnet-4-7")
112
142
 
113
- def test_env_missing_token_propagates_error(self):
114
- with self.assertRaises(H.HermesError):
115
- H.build_env(creds_path=Path("/no/such/file"))
143
+ def test_env_no_creds_no_anthropic_returns_env_without_token(self):
144
+ """When neither an Anthropic key nor Claude OAuth is available,
145
+ build_env returns a usable env WITHOUT either token. The caller
146
+ (or Hermès itself) decides whether to error or skip; build_env
147
+ no longer raises — that was the v0.18 strict behaviour."""
148
+ old_anth = os.environ.get("ANTHROPIC_API_KEY")
149
+ old_home = os.environ.get("OMEGA_HOME")
150
+ os.environ.pop("ANTHROPIC_API_KEY", None)
151
+ with tempfile.TemporaryDirectory() as tmp:
152
+ os.environ["OMEGA_HOME"] = tmp
153
+ try:
154
+ env = H.build_env(creds_path=Path("/no/such/file"))
155
+ self.assertNotIn("ANTHROPIC_API_KEY", env)
156
+ self.assertNotIn("CLAUDE_CODE_OAUTH_TOKEN", env)
157
+ self.assertEqual(env["HERMES_PROVIDER"], "anthropic")
158
+ finally:
159
+ os.environ.pop("OMEGA_HOME", None)
160
+ if old_home is not None:
161
+ os.environ["OMEGA_HOME"] = old_home
162
+ if old_anth is not None:
163
+ os.environ["ANTHROPIC_API_KEY"] = old_anth
116
164
 
117
165
 
118
166
  # ---------------------------------------------------------------------------
@@ -142,10 +142,15 @@ class TestTopicStats(unittest.TestCase):
142
142
  self.assertEqual(by_topic[2]["messages"], 1)
143
143
 
144
144
 
145
- class TestDaemonDMRefusal(unittest.TestCase):
146
- """The daemon must refuse to start a mission from a DM."""
147
- def test_dm_message_is_refused(self):
148
- from omega_engine.daemons.telegram import _route_one
145
+ class TestDaemonDMRoutesToAISBChat(unittest.TestCase):
146
+ """The daemon now routes DMs to the AISB conversational agent
147
+ (v0.19.13 change). Previously it refused with "project topic only",
148
+ which made the bot mute on a fresh install with no group yet."""
149
+
150
+ def test_dm_message_routes_to_aisb_chat(self):
151
+ from omega_engine.daemons import telegram as T
152
+ from omega_engine import aisb_chat
153
+ from unittest import mock
149
154
 
150
155
  class _FakeBridge:
151
156
  def __init__(self):
@@ -161,22 +166,65 @@ class TestDaemonDMRefusal(unittest.TestCase):
161
166
  with tempfile.TemporaryDirectory() as td:
162
167
  home = Path(td)
163
168
  bridge = _FakeBridge()
164
- tag = _route_one(
165
- topic_id=None, text="hi from DM",
166
- message_id=5, chat_id=12345,
167
- supervisor=_FakeSupervisor(),
168
- project_map={},
169
- home=home,
170
- telegram_bridge=bridge,
171
- )
172
- self.assertEqual(tag, "dm:refused")
173
- # We replied to the user with the redirect message.
169
+ with mock.patch.object(aisb_chat, "chat_once",
170
+ return_value="hello from AISB"):
171
+ tag = T._route_one(
172
+ topic_id=None, text="hi from DM",
173
+ message_id=5, chat_id=12345,
174
+ supervisor=_FakeSupervisor(),
175
+ project_map={},
176
+ home=home,
177
+ telegram_bridge=bridge,
178
+ )
179
+ self.assertEqual(tag, "dm:aisb-chat",
180
+ "DM must now be tagged as routed to AISB, not refused")
181
+ # We sent the AISB reply back via sendMessage.
174
182
  self.assertEqual(len(bridge.calls), 1)
175
183
  self.assertEqual(bridge.calls[0]["method"], "sendMessage")
176
184
  self.assertEqual(
177
185
  str(bridge.calls[0]["fields"]["chat_id"]), "12345",
178
186
  )
179
- self.assertIn("project topic", bridge.calls[0]["fields"]["text"])
187
+ self.assertEqual(
188
+ bridge.calls[0]["fields"]["text"], "hello from AISB",
189
+ )
190
+
191
+ def test_dm_history_is_namespaced_by_chat_id(self):
192
+ """Two DMs from different chats must NOT cross-contaminate history.
193
+ We use negative topic_ids derived from chat_id (real topics are
194
+ always positive Telegram forum thread IDs)."""
195
+ from omega_engine.daemons import telegram as T
196
+ from omega_engine import aisb_chat
197
+ from unittest import mock
198
+
199
+ class _FakeBridge:
200
+ def __init__(self): self.calls = []
201
+ def _call(self, method, fields, **_kw):
202
+ self.calls.append({"method": method, "fields": fields})
203
+ return {"message_id": 1}
204
+
205
+ class _FakeSupervisor:
206
+ def on_channel_message(self, t, x): return []
207
+
208
+ with tempfile.TemporaryDirectory() as td:
209
+ home = Path(td)
210
+ (home / "Agentik_Runtime").mkdir(parents=True)
211
+ captured: list[int] = []
212
+ def _fake_chat(home_arg, text, *, topic_id, record_history):
213
+ captured.append(topic_id)
214
+ return "ok"
215
+ with mock.patch.object(aisb_chat, "chat_once",
216
+ side_effect=_fake_chat):
217
+ T._route_one(topic_id=None, text="hi", message_id=1,
218
+ chat_id=42, supervisor=_FakeSupervisor(),
219
+ project_map={}, home=home,
220
+ telegram_bridge=_FakeBridge())
221
+ T._route_one(topic_id=None, text="hi", message_id=2,
222
+ chat_id=99, supervisor=_FakeSupervisor(),
223
+ project_map={}, home=home,
224
+ telegram_bridge=_FakeBridge())
225
+ self.assertEqual(captured, [-42, -99],
226
+ "DM history must be namespaced by chat_id "
227
+ "(encoded as negative topic_id)")
180
228
 
181
229
  def test_topic_message_records_history(self):
182
230
  from omega_engine.daemons.telegram import _route_one
@@ -1 +1 @@
1
- 0.19.12
1
+ 0.19.14
@@ -1,90 +1,147 @@
1
1
  # OmegaOS Layered Architecture
2
2
 
3
- > Five layers. Each independently usable. All five share ONE credential:
4
- > the Claude Code Max OAuth token at `~/.claude/.credentials.json`.
3
+ > Five layers. **Two independent credential domains.** **Two Telegram bots
4
+ > with separate tokens.** Hermès sits above; AISB+Oracle+Workers stay below.
5
+ > Each layer is independently usable.
6
+
7
+ ## The corrected model (v0.19.14)
5
8
 
6
9
  ```
7
10
  ┌──────────────────────────────────────────────────────────────────────┐
8
- │ Layer 1 ─ Human
9
- Telegram / CLI / web — operator intent in natural language
10
- └────────────────────────────────────────┬─────────────────────────────┘
11
-
12
- ┌────────────────────────────────────────▼─────────────────────────────┐
13
- │ Layer 2 ─ Hermes (Nous Research, opt-in) │
14
- Autonomous self-improving agent. Cross-platform gateway, scheduled
15
- │ automations, skill creation from experience, dialectic user model. │
16
- │ Uses CLAUDE_CODE_OAUTH_TOKEN from ~/.claude/.credentials.json. │
17
- Bridge: `omega hermes {install,link,status,ask}`
18
- └────────────────────────────────────────┬─────────────────────────────┘
19
-
20
- ┌────────────────────────────────────────▼─────────────────────────────┐
21
- Layer 3 AISB (Agentik Intake Service Bus)
22
- Telegram-first intake. Classifies the message, picks a topology,
23
- dispatches a mission to the Engine. Lives in $OMEGA_HOME.
24
- └────────────────────────────────────────┬─────────────────────────────┘
25
-
26
- ┌────────────────────────────────────────▼─────────────────────────────┐
27
- Layer 4 Oracle (the planner)
28
- Reads the mission, plans 1..N worker subtasks, each with a
29
- │ verify_cmd + file ownership. Persistent tmux session. │
30
- └────────────────────────────────────────┬─────────────────────────────┘
31
-
32
- ┌────────────────────────────────────────▼─────────────────────────────┐
33
- Layer 5Workers (executors)
34
- One per subtask. Persistent tmux session. Runs `claude -p` with
35
- the worker envelope + injected skill palette. Writes .done.json
36
- on completion. Verified by the audit gate before the parent's
37
- │ barrier resolves. │
38
- └──────────────────────────────────────────────────────────────────────┘
11
+ │ Layer 1 ─ Human (you)
12
+ Three doors:
13
+ │ 1. Telegram → Hermès bot (meta-companion, Anthropic API) │
14
+ 2. Telegram → AISB bot (OmegaOS native, Claude Max OAuth) │
15
+ │ 3. CLI / tmux (omega …, AISB-chat session) │
16
+ └────────┬───────────────────────────────────┬─────────────────────────┘
17
+
18
+ ▼ ▼
19
+ ┌──────────────────────┐ ┌────────────────────────────────────┐
20
+ Layer 2 ─ HERMÈS │ Layer 3 ─ AISB │
21
+ │ Meta-companion, │ ───────►│ OmegaOS intake / orchestrator │
22
+ scheduler, learner. │ dispatch│ Classifies, picks a topology, │
23
+ │ │ missions│ runs the mission. │
24
+ Auth: ANTHROPIC_API_ │ │
25
+ KEY_HERMES │ │ Auth: Claude Max OAuth
26
+ Bot: TELEGRAM_TOKEN_│ │ (~/.claude/.credentials.json)
27
+ │ HERMES │ │ Bot: TELEGRAM_TOKEN_AISB │
28
+ │ │ │
29
+ │ OPT-IN (skip if you │ │ Required for missions. │
30
+ don't want a paid │ │ Telegram bot is optional too —
31
+ Anthropic key) │ │ tmux AISB-chat is the fallback.
32
+ └──────────────────────┘ └─────────────┬──────────────────────┘
33
+
34
+
35
+ ┌────────────────────────────────────┐
36
+ Layer 4Oracle (planner)
37
+ │ Persistent tmux. Reads mission,
38
+ plans N workers with verify_cmd.
39
+ Auth: Claude Max OAuth
40
+ └─────────────┬──────────────────────┘
41
+
42
+
43
+ ┌────────────────────────────────────┐
44
+ │ Layer 5 ─ Workers (executors) │
45
+ │ One per subtask. Persistent tmux. │
46
+ │ Writes .done.json. Audit-gated. │
47
+ │ Auth: Claude Max OAuth │
48
+ └────────────────────────────────────┘
49
+ ```
50
+
51
+ ## Why TWO bots, TWO auth domains
52
+
53
+ | Concern | Hermès (L2) | AISB+Oracle+Workers (L3-L5) |
54
+ |---|---|---|
55
+ | **Telegram token** | `TELEGRAM_TOKEN_HERMES` | `TELEGRAM_TOKEN_AISB` |
56
+ | **LLM credential** | `ANTHROPIC_API_KEY_HERMES` (you pay per call) | Claude Max OAuth from `~/.claude/.credentials.json` (covered by your Max sub) |
57
+ | **What it does** | Meta-reasoning: schedules, suggestions, learning, dispatches missions DOWN to AISB | Executes verified-completion missions inside projects |
58
+ | **Budget risk** | You pay → bounded by your Anthropic spend limit | Claude Max — never charged per call |
59
+ | **Failure mode if absent** | Skip Hermès → OmegaOS still works (CLI + AISB bot) | If absent, no missions → not optional |
60
+
61
+ This split is intentional:
62
+
63
+ 1. **Budget isolation.** Hermès can run a tight schedule loop, learn from
64
+ observations, propose missions — without ever touching your Max quota.
65
+ AISB+Workers can crank through 50-step missions on Max OAuth — without
66
+ ever burning your Anthropic API budget.
67
+ 2. **Independent identity.** Hermès has its own Telegram username, its
68
+ own bot personality, its own conversation history. AISB stays
69
+ project-focused (one bot per OmegaOS install, topics per project).
70
+ 3. **Authority gradient.** Hermès is *above* OmegaOS — it can issue
71
+ commands to AISB but doesn't run inside it. AISB is *the* runtime
72
+ for OmegaOS missions.
73
+
74
+ ## Per-project Telegram structure
75
+
76
+ OmegaOS treats Telegram as the operator's command surface:
77
+
78
+ ```
79
+ AISB Group (Telegram supergroup with topics enabled)
80
+
81
+ ├── Topic "general" — chat with AISB, status questions
82
+ ├── Topic "project: dentistry" — dispatch missions for the Dentistry project
83
+ ├── Topic "project: kommu" — dispatch missions for the Kommu project
84
+ ├── Topic "project: causio" — dispatch missions for the Causio project
85
+ └── … (one topic per project, mapped in
86
+ Agentik_SSOT/projects/<slug>/registry.yaml)
39
87
  ```
40
88
 
41
- ## Why this shape
89
+ A message posted in a project topic = a mission for THAT project. AISB
90
+ reads `message_thread_id` (the topic_id), looks up the slug, runs the
91
+ mission in the project's context (cwd = project root, env =
92
+ project-scoped secrets).
93
+
94
+ Hermès operates ABOVE the group. It can:
95
+
96
+ * Receive a DM in **its own** Telegram bot from you.
97
+ * Decide: "this is a mission for project X" → send a message INTO
98
+ the AISB group's topic-X (Hermès posts as a member of the group).
99
+ * Or: "this is a meta-question" → answer directly via Anthropic API.
100
+ * Or: "this is an autonomous trigger" (cron, observation) → dispatch
101
+ a fresh mission into a project topic.
42
102
 
43
- | Layer | Role | Runtime | Credential |
44
- |---|---|---|---|
45
- | 1 | Intent | Human | n/a |
46
- | 2 | Autonomous companion | Hermes (Python, ~/.hermes) | Claude Max OAuth |
47
- | 3 | Mission intake | OmegaOS engine | Claude Max OAuth |
48
- | 4 | Planning | Persistent tmux + `claude -p` | Claude Max OAuth |
49
- | 5 | Execution | Persistent tmux + `claude -p` | Claude Max OAuth |
103
+ ## Install / secrets layout (corrected)
50
104
 
51
- Every layer below #1 calls Claude through the same OAuth credential. No
52
- API key burn, no separate auth surface, no extra account to manage. The
53
- token rotates server-side via the standard Claude Code refresh flow;
54
- every layer reads the live file (`~/.claude/.credentials.json`) on each
55
- call so refresh propagates automatically.
105
+ | Vault key | Holds | Who reads it |
106
+ |---|---|---|
107
+ | `TELEGRAM_TOKEN_AISB` | AISB bot token (from BotFather, bot A) | `omega telegram bridge` + AISB daemon |
108
+ | `TELEGRAM_TOKEN_HERMES` | Hermès bot token (from BotFather, bot B) | Hermès daemon only |
109
+ | `ANTHROPIC_API_KEY_HERMES` | Hermès's own paid Anthropic key | Hermès daemon only |
110
+ | `TELEGRAM_GROUP_ID` | AISB supergroup chat_id | AISB daemon (to scope missions) |
111
+ | `TELEGRAM_TOKEN` | **back-compat alias** for `TELEGRAM_TOKEN_AISB` | Old install paths |
112
+ | Claude Max OAuth | The OAuth at `~/.claude/.credentials.json` | AISB, Oracle, all Workers |
113
+
114
+ Claude Max OAuth is **never duplicated in the vault** — it lives in
115
+ `~/.claude/.credentials.json` and every layer below Hermès reads it live
116
+ on every call so refresh rotations propagate naturally.
56
117
 
57
118
  ## Layer choice cheat-sheet
58
119
 
59
120
  | You want to | Use |
60
121
  |---|---|
61
- | One-shot CLI question | Layer 5 directly: `claude -p "..."` |
62
- | Verified-completion multi-worker mission | Layer 3-5: `omega run "..."` |
63
- | Recurring scheduled mission (cron) | Layer 3-5: `omega cadence schedule ...` |
64
- | Cross-platform chat (Telegram, Discord, …) | Layer 2: `hermes gateway` |
65
- | Skill creation from experience over time | Layer 2: Hermes's learning loop |
66
- | Long-running autonomous research | Layer 2: Hermes + scheduled task |
67
- | Visual codebase exploration | Sidecar: `omega ua run <project>` |
68
-
69
- ## Bridges
70
-
71
- * `omega hermes` — Layer 2 ↔ rest of stack. Hermes can call OmegaOS via
72
- the `omega` CLI (any of layers 3-5). OmegaOS can call Hermes via
73
- `omega hermes ask "..."`.
74
- * `omega ua` — Understand-Anything sidecar. Builds an interactive
75
- knowledge graph of any project; `omega ua consume` lands the graph
76
- into the Engine's GraphRetriever so Oracle/workers can answer
77
- "where in the code is X?" via Graph RAG.
78
- * `omega ma` — Anthropic Managed Agents (opt-in, API-key-only). NOT
79
- part of the OAuth stack; only used when explicitly opted into per
80
- topology entry.
122
+ | Quick chat with the meta-companion | Hermès bot (DM) Anthropic API |
123
+ | Run a mission on a project | AISB project topic OR `omega run` from project dir |
124
+ | One-shot CLI question (no orchestration) | `claude -p "..."` directly |
125
+ | Verified-completion multi-worker mission | `omega run` / AISB topic engages Oracle + Workers |
126
+ | Recurring scheduled mission | `omega cadence schedule …` wakes through AISB |
127
+ | Cross-platform autonomous loop | Hermès (Anthropic API key, scheduled tasks) |
128
+ | Audit a project | `/codeaudit` etc. from project — Quality Arsenal |
81
129
 
82
130
  ## What this is NOT
83
131
 
84
- * Hermes is not a "wrapper" around OmegaOSit has its own loop, its
85
- own skills, its own memory. The bridge gives both runtimes the
86
- ability to delegate to each other.
87
- * Layer 2 is OPTIONAL. OmegaOS without Hermes is a complete agentic
88
- OS (layers 1+3-5). Adding Hermes buys you the autonomous companion
89
- + cross-platform gateway + learning loop. Removing Hermes removes
90
- none of OmegaOS's verified-completion guarantees.
132
+ * Hermès is NOT a wrapper around AISBthey're separate runtimes with
133
+ separate credentials and separate Telegram identities. The bridge gives
134
+ both the ability to delegate to each other.
135
+ * AISB does NOT need Hermès to function. Skip Hermès install → OmegaOS
136
+ is a complete agentic OS with verified completion. Add Hermès when you
137
+ want meta-reasoning + autonomous loops above your projects.
138
+ * The Anthropic API key Hermès uses is NOT shared with anything else.
139
+ AISB+Oracle+Workers stay on Max OAuth, always.
140
+
141
+ ## v0.19.x evolution
142
+
143
+ | Version | Change |
144
+ |---|---|
145
+ | v0.18 | Single OAuth model (Hermès on Claude Max) |
146
+ | **v0.19.14** | **Split: Hermès on `ANTHROPIC_API_KEY_HERMES`; AISB on Max OAuth; two Telegram bots** |
147
+ | (planned v0.19.15+) | Dedicated Hermès daemon (its own poll loop), Hermès→AISB dispatch verbs |
@@ -0,0 +1,17 @@
1
+ # References
2
+
3
+ Operator-curated source-of-truth documents that OmegaOS skills + Genesis
4
+ consume by name. Drop a new version of any file here and it propagates
5
+ to every project at the next `omega sync`.
6
+
7
+ ## Active references
8
+
9
+ | File | Consumed by | Topic |
10
+ |---|---|---|
11
+ | `CLAUDE-DESKTOP-FRONTEND-ARCHITECTURE.md` | `/desktop-architecture`, Genesis stack `claude-dashboard`, refonte audit | Premium desktop / dashboard UI architecture, the "Claude / Linear / Vercel / Stripe / Elevenlabs" canon |
12
+
13
+ ## Format
14
+
15
+ Plain Markdown. No frontmatter needed — these are reference docs, not
16
+ SKILL.md. Headings should be self-describing so skills can extract
17
+ sub-sections by regex if needed.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: desktop-architecture
3
+ description: >
4
+ Reference skill — pull the canonical Claude Code-grade desktop/dashboard
5
+ architecture (`Agentik_SSOT/docs/references/CLAUDE-DESKTOP-FRONTEND-ARCHITECTURE.md`)
6
+ and apply it to a project. Triggers on "desktop architecture", "Claude
7
+ dashboard", "Claude desktop", "premium dashboard", "comme Linear / Vercel
8
+ / Stripe / Elevenlabs", "dashboard senior", "claude stack". Use whenever
9
+ the user asks for a dashboard refonte or a new project that needs the
10
+ Claude-grade desktop look.
11
+ when_to_use: >
12
+ User mentions "desktop architecture", "Claude dashboard", "Claude desktop
13
+ stack", or asks for a dashboard rebuild "comme Claude / Linear / Vercel /
14
+ Stripe / Elevenlabs". Also triggers automatically when the Genesis stack
15
+ picker selects the `claude-dashboard` preset.
16
+ allowed-tools: Read Grep Glob
17
+ ---
18
+
19
+ # /desktop-architecture — pull the canonical Claude dashboard reference
20
+
21
+ This skill loads the **Claude Desktop Frontend Architecture** reference
22
+ document from the SSOT and applies its principles to the current task.
23
+
24
+ ## What it does
25
+
26
+ 1. Reads `$OMEGA_HOME/Agentik_SSOT/docs/references/CLAUDE-DESKTOP-FRONTEND-ARCHITECTURE.md`
27
+ 2. Extracts the relevant section (components, layout, motion, palette,
28
+ typography, density, navigation, command palette, etc.)
29
+ 3. Applies it as the design contract for the requested task:
30
+ - **Refonte audit** — score the current UI against this contract
31
+ - **Genesis** — when stack = `claude-dashboard`, the PRD, branding and
32
+ design-system phases derive their tokens from this doc
33
+ - **Component builder** — when a worker generates a new dashboard
34
+ screen, it references the layout primitives from this doc first
35
+
36
+ ## Triggers (any of these in a user prompt invoke me)
37
+
38
+ - `desktop architecture`
39
+ - `Claude desktop` / `Claude dashboard` / `Claude stack`
40
+ - `premium dashboard` / `dashboard senior`
41
+ - `comme Linear` / `comme Vercel` / `comme Stripe` / `comme Elevenlabs`
42
+ - `refais le dashboard` / `redesign dashboard` (combined with project name)
43
+
44
+ ## Where it lives
45
+
46
+ The reference doc itself lives at:
47
+ `$OMEGA_HOME/Agentik_SSOT/docs/references/CLAUDE-DESKTOP-FRONTEND-ARCHITECTURE.md`
48
+
49
+ It's the single source of truth — when Gareth refreshes the document
50
+ (e.g. new components, new motion language), every project re-reads it
51
+ on next `/desktop-architecture` invocation. No fork, no drift.
52
+
53
+ ## How to refresh the reference
54
+
55
+ The operator drops a new version into the SSOT path above, then runs:
56
+
57
+ ```bash
58
+ omega sync # projects to ~/.claude/skills/<id>/
59
+ ```
60
+
61
+ Workers spawned after the sync pick up the new content automatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentikos/omega-os",
3
- "version": "0.19.12",
3
+ "version": "0.19.14",
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"