@agentikos/omega-os 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -13
- package/bootstrap/lib/steps.sh +214 -9
- package/bootstrap/manifest.example.yaml +6 -1
- package/docs/COMPLETION-PLAN.md +48 -0
- package/omega/Agentik_Engine/README.md +25 -10
- package/omega/Agentik_Engine/omega_engine/__init__.py +66 -2
- package/omega/Agentik_Engine/omega_engine/account.py +505 -0
- package/omega/Agentik_Engine/omega_engine/autonomous.py +538 -0
- package/omega/Agentik_Engine/omega_engine/cli.py +467 -29
- package/omega/Agentik_Engine/omega_engine/daemons/__init__.py +14 -0
- package/omega/Agentik_Engine/omega_engine/daemons/autonomous.py +56 -0
- package/omega/Agentik_Engine/omega_engine/daemons/engine.py +187 -0
- package/omega/Agentik_Engine/omega_engine/daemons/telegram.py +231 -0
- package/omega/Agentik_Engine/omega_engine/educators/__init__.py +51 -0
- package/omega/Agentik_Engine/omega_engine/educators/artifact.py +65 -0
- package/omega/Agentik_Engine/omega_engine/educators/automation.py +76 -0
- package/omega/Agentik_Engine/omega_engine/educators/base.py +327 -0
- package/omega/Agentik_Engine/omega_engine/educators/claudecode.py +71 -0
- package/omega/Agentik_Engine/omega_engine/educators/connection.py +75 -0
- package/omega/Agentik_Engine/omega_engine/educators/coworker.py +68 -0
- package/omega/Agentik_Engine/omega_engine/educators/loop.py +82 -0
- package/omega/Agentik_Engine/omega_engine/educators/prompt.py +68 -0
- package/omega/Agentik_Engine/omega_engine/educators/skill.py +69 -0
- package/omega/Agentik_Engine/omega_engine/executor.py +46 -6
- package/omega/Agentik_Engine/omega_engine/mission.py +13 -1
- package/omega/Agentik_Engine/omega_engine/provider.py +247 -1
- package/omega/Agentik_Engine/omega_engine/rag/__init__.py +21 -0
- package/omega/Agentik_Engine/omega_engine/rag/agentic.py +83 -0
- package/omega/Agentik_Engine/omega_engine/rag/base.py +42 -0
- package/omega/Agentik_Engine/omega_engine/rag/corrective.py +119 -0
- package/omega/Agentik_Engine/omega_engine/rag/graph.py +169 -0
- package/omega/Agentik_Engine/omega_engine/rag/hybrid.py +205 -0
- package/omega/Agentik_Engine/omega_engine/rag/multimodal.py +136 -0
- package/omega/Agentik_Engine/omega_engine/rag/router.py +110 -0
- package/omega/Agentik_Engine/omega_engine/reducer.py +21 -3
- package/omega/Agentik_Engine/omega_engine/store.py +65 -5
- package/omega/Agentik_Engine/omega_engine/sync.py +304 -0
- package/omega/Agentik_Engine/omega_engine/tools.py +272 -0
- package/omega/Agentik_Engine/pyproject.toml +1 -1
- package/omega/Agentik_Engine/tests/test_account.py +333 -0
- package/omega/Agentik_Engine/tests/test_autonomous.py +361 -0
- package/omega/Agentik_Engine/tests/test_educators.py +233 -0
- package/omega/Agentik_Engine/tests/test_rag.py +287 -0
- package/omega/Agentik_Engine/tests/test_snapshot_partial.py +172 -0
- package/omega/Agentik_Engine/tests/test_tools_and_sync.py +312 -0
- package/omega/Agentik_SSOT/skills/rag-route.md +73 -0
- package/package.json +1 -1
- package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit_arsenal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/barrier.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/bus.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/events.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/executor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/mission.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/progress.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/project.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/provider.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/reducer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/report.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/router.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/store.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/supervisor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/task.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/telegram.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audit_arsenal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_executor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_mission.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_progress.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_project.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_reducer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_report.cpython-313.pyc +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""The `omega` command-line interface.
|
|
2
2
|
|
|
3
3
|
A thin entry point. The installer's doctor step calls `omega doctor`; operators
|
|
4
|
-
use `omega status` to see every task's derived state.
|
|
4
|
+
use `omega status` to see every task's derived state. `omega account ...` and
|
|
5
|
+
`omega billing` give visibility into the Claude Code Max account pool — see
|
|
6
|
+
`docs/ACCOUNT-AND-BILLING.md` for the design.
|
|
5
7
|
"""
|
|
6
8
|
from __future__ import annotations
|
|
7
9
|
|
|
@@ -62,43 +64,237 @@ def cmd_status(_args: argparse.Namespace) -> int:
|
|
|
62
64
|
return 0
|
|
63
65
|
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
"""Show the Claude Code Max account pool and the selection strategy.
|
|
67
|
+
# ──── account subcommands ─────────────────────────────────────────────────
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
|
|
70
|
+
def _print_pool(pool) -> None:
|
|
71
|
+
print(f"Claude Max account pool — selection: {pool.selection}")
|
|
72
|
+
if not pool.accounts:
|
|
73
|
+
print(" (empty)")
|
|
74
|
+
print(" add an account: omega account login")
|
|
75
|
+
return
|
|
76
|
+
for a in pool.accounts:
|
|
77
|
+
used = f"{a.tokens_used:>9,d} tok" if a.tokens_used else " -"
|
|
78
|
+
print(f" [{a.status:<9}] {a.id:<16} {used} {a.label}")
|
|
79
|
+
print(" add an account: omega account login")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def cmd_account_list(_args: argparse.Namespace) -> int:
|
|
83
|
+
"""Show the Claude Code Max account pool and the selection strategy."""
|
|
84
|
+
from omega_engine.account import AccountPool
|
|
85
|
+
|
|
86
|
+
pool = AccountPool.load(_omega_home())
|
|
87
|
+
_print_pool(pool)
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def cmd_account_login(args: argparse.Namespace) -> int:
|
|
92
|
+
"""Add an account: run the OAuth flow, store the token in the vault,
|
|
93
|
+
and add a pool entry.
|
|
94
|
+
|
|
95
|
+
Strategy: try the RFC 8628 device-code flow first; if it's unavailable
|
|
96
|
+
(Anthropic does not currently publish those endpoints), fall back to a
|
|
97
|
+
manual paste flow — the user logs in via browser elsewhere, copies the
|
|
98
|
+
token, and pastes it. Either way ends with the token in the vault and
|
|
99
|
+
a new account in `accounts.yaml`.
|
|
72
100
|
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
from omega_engine.account import (
|
|
102
|
+
AccountPool,
|
|
103
|
+
ClaudeAccount,
|
|
104
|
+
claude_device_code_flow,
|
|
105
|
+
claude_oauth_login_url,
|
|
106
|
+
write_token,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
account_id = (args.id or "").strip()
|
|
110
|
+
if not account_id:
|
|
111
|
+
account_id = input("account id (e.g. max-primary): ").strip()
|
|
112
|
+
if not account_id:
|
|
113
|
+
print("account id is required — aborting")
|
|
114
|
+
return 2
|
|
115
|
+
label = (args.label or "").strip() or f"Claude Max account ({account_id})"
|
|
116
|
+
secret_ref = f"CLAUDE_OAUTH_{account_id}"
|
|
117
|
+
|
|
118
|
+
token: str | None = None
|
|
119
|
+
if not args.manual:
|
|
120
|
+
print("attempting OAuth device-code flow...")
|
|
121
|
+
try:
|
|
122
|
+
result = claude_device_code_flow()
|
|
123
|
+
instruction = result.get("raw", {}).get("_human_instruction") \
|
|
124
|
+
or result.get("_human_instruction") or ""
|
|
125
|
+
if instruction:
|
|
126
|
+
print(instruction)
|
|
127
|
+
token = result.get("access_token")
|
|
128
|
+
except RuntimeError as exc:
|
|
129
|
+
print(f"device-code flow unavailable: {exc}")
|
|
130
|
+
print("falling back to manual paste...")
|
|
131
|
+
|
|
132
|
+
if not token:
|
|
133
|
+
# manual fallback — this is the path that always works
|
|
134
|
+
print()
|
|
135
|
+
print("MANUAL LOGIN")
|
|
136
|
+
print(f" 1. open in a browser: {claude_oauth_login_url()}")
|
|
137
|
+
print(" 2. complete login on the Claude account you want to add")
|
|
138
|
+
print(" 3. copy the OAuth token from the dashboard or developer panel")
|
|
139
|
+
print()
|
|
140
|
+
try:
|
|
141
|
+
token = input("paste the OAuth token here: ").strip()
|
|
142
|
+
except EOFError:
|
|
143
|
+
token = ""
|
|
144
|
+
if not token:
|
|
145
|
+
print("no token provided — aborting (vault not modified)")
|
|
146
|
+
return 2
|
|
147
|
+
|
|
148
|
+
home = _omega_home()
|
|
149
|
+
path = write_token(home, secret_ref, token)
|
|
150
|
+
pool = AccountPool.load(home)
|
|
151
|
+
pool.add(ClaudeAccount(
|
|
152
|
+
id=account_id, label=label, secret_ref=secret_ref,
|
|
153
|
+
weight=1, status="active",
|
|
154
|
+
))
|
|
155
|
+
cfg = pool.save(home)
|
|
156
|
+
print(f" vault: {path} (chmod 600)")
|
|
157
|
+
print(f" pool: {cfg}")
|
|
158
|
+
_print_pool(pool)
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def cmd_account_use(args: argparse.Namespace) -> int:
|
|
163
|
+
"""Set an account's status: active | resting | disabled."""
|
|
164
|
+
from omega_engine.account import AccountPool
|
|
165
|
+
|
|
166
|
+
home = _omega_home()
|
|
167
|
+
pool = AccountPool.load(home)
|
|
168
|
+
if pool.get(args.id) is None:
|
|
169
|
+
print(f"no account '{args.id}' in the pool")
|
|
170
|
+
return 2
|
|
79
171
|
try:
|
|
80
|
-
|
|
81
|
-
except
|
|
82
|
-
print(
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
print(f"Claude Max account pool — selection: {data.get('selection', 'least-used')}")
|
|
87
|
-
for a in pool or [{"status": "(empty)", "id": "", "label": ""}]:
|
|
88
|
-
print(f" [{str(a.get('status', '?')):<9}] {str(a.get('id', '')):<16} {a.get('label', '')}")
|
|
89
|
-
print(" add an account: omega account login (docs/ACCOUNT-AND-BILLING.md)")
|
|
172
|
+
pool.set_status(args.id, args.status)
|
|
173
|
+
except ValueError as exc:
|
|
174
|
+
print(str(exc))
|
|
175
|
+
return 2
|
|
176
|
+
pool.save(home)
|
|
177
|
+
_print_pool(pool)
|
|
90
178
|
return 0
|
|
91
179
|
|
|
92
180
|
|
|
181
|
+
def cmd_account_pool(args: argparse.Namespace) -> int:
|
|
182
|
+
"""Edit pool-wide knobs: selection strategy and per-account weights.
|
|
183
|
+
|
|
184
|
+
Non-interactive flags drive scripted edits; with no flags, it prints the
|
|
185
|
+
current pool for inspection.
|
|
186
|
+
"""
|
|
187
|
+
from omega_engine.account import AccountPool
|
|
188
|
+
|
|
189
|
+
home = _omega_home()
|
|
190
|
+
pool = AccountPool.load(home)
|
|
191
|
+
changed = False
|
|
192
|
+
if args.selection:
|
|
193
|
+
if args.selection not in ("round-robin", "least-used", "by-quota"):
|
|
194
|
+
print(
|
|
195
|
+
f"invalid selection '{args.selection}' — "
|
|
196
|
+
"must be round-robin|least-used|by-quota"
|
|
197
|
+
)
|
|
198
|
+
return 2
|
|
199
|
+
pool.selection = args.selection
|
|
200
|
+
changed = True
|
|
201
|
+
if args.weight:
|
|
202
|
+
# each --weight ID=N — parse and apply
|
|
203
|
+
for spec in args.weight:
|
|
204
|
+
if "=" not in spec:
|
|
205
|
+
print(f"weight spec '{spec}' must be of the form id=N")
|
|
206
|
+
return 2
|
|
207
|
+
ident, _, val = spec.partition("=")
|
|
208
|
+
ident, val = ident.strip(), val.strip()
|
|
209
|
+
acc = pool.get(ident)
|
|
210
|
+
if acc is None:
|
|
211
|
+
print(f"no account '{ident}' — skipping")
|
|
212
|
+
continue
|
|
213
|
+
try:
|
|
214
|
+
acc.weight = max(int(val), 0)
|
|
215
|
+
except ValueError:
|
|
216
|
+
print(f"weight for '{ident}' must be an integer, got {val!r}")
|
|
217
|
+
return 2
|
|
218
|
+
changed = True
|
|
219
|
+
if changed:
|
|
220
|
+
pool.save(home)
|
|
221
|
+
_print_pool(pool)
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def cmd_account(args: argparse.Namespace) -> int:
|
|
226
|
+
"""Account dispatcher — keeps `omega account` (no subcommand) working as a
|
|
227
|
+
list view, matches the original surface area."""
|
|
228
|
+
sub = getattr(args, "account_cmd", None) or "list"
|
|
229
|
+
fns = {
|
|
230
|
+
"list": cmd_account_list,
|
|
231
|
+
"login": cmd_account_login,
|
|
232
|
+
"use": cmd_account_use,
|
|
233
|
+
"pool": cmd_account_pool,
|
|
234
|
+
}
|
|
235
|
+
if sub not in fns:
|
|
236
|
+
print(f"unknown account subcommand: {sub}")
|
|
237
|
+
return 2
|
|
238
|
+
return fns[sub](args)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ──── billing ─────────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
|
|
93
244
|
def cmd_billing(_args: argparse.Namespace) -> int:
|
|
94
245
|
"""Show usage and cost per Claude Max account.
|
|
95
246
|
|
|
96
|
-
|
|
97
|
-
|
|
247
|
+
Sources: token usage on `task.*` events (live aggregation via
|
|
248
|
+
`BillingAggregator`) plus the cached counter in `accounts.yaml`. The event
|
|
249
|
+
log is the source of truth; the cached counter is the convenience view.
|
|
98
250
|
"""
|
|
251
|
+
from omega_engine.account import AccountPool, BillingAggregator
|
|
252
|
+
from omega_engine.store import SQLiteStore
|
|
253
|
+
|
|
254
|
+
home = _omega_home()
|
|
255
|
+
pool = AccountPool.load(home)
|
|
99
256
|
print("omega billing — usage per Claude Max account")
|
|
100
|
-
print(
|
|
101
|
-
print("
|
|
257
|
+
print()
|
|
258
|
+
print(f" pool selection: {pool.selection}")
|
|
259
|
+
print(f" accounts in pool: {len(pool.accounts)}")
|
|
260
|
+
print()
|
|
261
|
+
|
|
262
|
+
db = home / "Agentik_Runtime" / "eventlog" / "omega.db"
|
|
263
|
+
if not db.exists():
|
|
264
|
+
print(" no event store yet — nothing to aggregate")
|
|
265
|
+
return 0
|
|
266
|
+
store = SQLiteStore(db)
|
|
267
|
+
totals = BillingAggregator.from_event_log(store)
|
|
268
|
+
if not totals:
|
|
269
|
+
print(" no per-account usage recorded in the event log yet")
|
|
270
|
+
print(" (events emit `usage.account_id` once the Claude provider "
|
|
271
|
+
"is wired)")
|
|
272
|
+
store.close()
|
|
273
|
+
return 0
|
|
274
|
+
|
|
275
|
+
pool_ids = {a.id for a in pool.accounts}
|
|
276
|
+
header = f" {'account':<16} {'calls':>7} {'input_tok':>12} {'output_tok':>12} {'total_tok':>12}"
|
|
277
|
+
print(header)
|
|
278
|
+
print(f" {'-' * 64}")
|
|
279
|
+
grand_in = grand_out = grand_calls = 0
|
|
280
|
+
for account_id, t in sorted(totals.items()):
|
|
281
|
+
marker = "" if account_id in pool_ids else " (unknown)"
|
|
282
|
+
total_tok = t["input_tokens"] + t["output_tokens"]
|
|
283
|
+
print(
|
|
284
|
+
f" {account_id:<16} {t['total_calls']:>7,d} "
|
|
285
|
+
f"{t['input_tokens']:>12,d} {t['output_tokens']:>12,d} "
|
|
286
|
+
f"{total_tok:>12,d}{marker}"
|
|
287
|
+
)
|
|
288
|
+
grand_in += t["input_tokens"]
|
|
289
|
+
grand_out += t["output_tokens"]
|
|
290
|
+
grand_calls += t["total_calls"]
|
|
291
|
+
print(f" {'-' * 64}")
|
|
292
|
+
print(
|
|
293
|
+
f" {'TOTAL':<16} {grand_calls:>7,d} "
|
|
294
|
+
f"{grand_in:>12,d} {grand_out:>12,d} "
|
|
295
|
+
f"{grand_in + grand_out:>12,d}"
|
|
296
|
+
)
|
|
297
|
+
store.close()
|
|
102
298
|
return 0
|
|
103
299
|
|
|
104
300
|
|
|
@@ -134,13 +330,227 @@ def cmd_project(args: argparse.Namespace) -> int:
|
|
|
134
330
|
return 0
|
|
135
331
|
|
|
136
332
|
|
|
137
|
-
|
|
333
|
+
# --------------------------------------------------------------------------
|
|
334
|
+
# `omega tool ...` — the registry of third-party tools (incl. installed MCPs).
|
|
335
|
+
# --------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _tool_install_npm(name: str, package: str, home: Path) -> dict[str, str]:
|
|
339
|
+
"""Install one npm package into Agentik_Tools/<name>/ and return locator info."""
|
|
340
|
+
import shutil as _sh
|
|
341
|
+
import subprocess
|
|
342
|
+
|
|
343
|
+
tool_dir = home / "Agentik_Tools" / name
|
|
344
|
+
tool_dir.mkdir(parents=True, exist_ok=True)
|
|
345
|
+
npm = _sh.which("npm")
|
|
346
|
+
if npm is None:
|
|
347
|
+
raise RuntimeError("npm not found on PATH — install Node.js first")
|
|
348
|
+
subprocess.run(
|
|
349
|
+
[npm, "install", "-g", "--prefix", str(tool_dir), package],
|
|
350
|
+
check=True, capture_output=True, text=True, timeout=600,
|
|
351
|
+
)
|
|
352
|
+
bin_dir = tool_dir / "bin"
|
|
353
|
+
invoke = ""
|
|
354
|
+
if bin_dir.is_dir():
|
|
355
|
+
bins = sorted(p for p in bin_dir.iterdir() if p.is_file())
|
|
356
|
+
if bins:
|
|
357
|
+
invoke = str(bins[0].relative_to(home))
|
|
358
|
+
return {"path": f"Agentik_Tools/{name}/", "invoke": invoke}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def cmd_tool_list(_args: argparse.Namespace) -> int:
|
|
362
|
+
from omega_engine.tools import ToolRegistry
|
|
363
|
+
reg = ToolRegistry.load(_omega_home())
|
|
364
|
+
tools = reg.list()
|
|
365
|
+
if not tools:
|
|
366
|
+
print("(no tools installed)")
|
|
367
|
+
return 0
|
|
368
|
+
print(f"{len(tools)} tool(s) installed:")
|
|
369
|
+
for t in tools:
|
|
370
|
+
ver = f"@{t.version}" if t.version else ""
|
|
371
|
+
print(f" {t.name}{ver} source={t.source or '-'} invoke={t.invoke or '-'}")
|
|
372
|
+
return 0
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def cmd_tool_install(args: argparse.Namespace) -> int:
|
|
376
|
+
"""Install a tool — either an MCP id from the catalog OR an npm package."""
|
|
377
|
+
home = _omega_home()
|
|
378
|
+
name = args.name
|
|
379
|
+
from omega_engine.tools import (
|
|
380
|
+
CatalogError,
|
|
381
|
+
Tool,
|
|
382
|
+
ToolRegistry,
|
|
383
|
+
install_from_catalog,
|
|
384
|
+
load_catalog,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
if args.npm is None:
|
|
388
|
+
# Default path: treat <name> as an MCP catalog id.
|
|
389
|
+
try:
|
|
390
|
+
catalog = load_catalog(home)
|
|
391
|
+
tool = install_from_catalog(home, name, catalog=catalog)
|
|
392
|
+
print(f"installed {tool.name} (source={tool.source})")
|
|
393
|
+
return 0
|
|
394
|
+
except CatalogError as exc:
|
|
395
|
+
print(f"error: {exc}")
|
|
396
|
+
return 1
|
|
397
|
+
except Exception as exc: # noqa: BLE001
|
|
398
|
+
print(f"error: {exc}")
|
|
399
|
+
return 1
|
|
400
|
+
|
|
401
|
+
# --npm given: install a bare npm package under Agentik_Tools/<name>/.
|
|
402
|
+
try:
|
|
403
|
+
loc = _tool_install_npm(name, args.npm, home)
|
|
404
|
+
except Exception as exc: # noqa: BLE001
|
|
405
|
+
print(f"error: {exc}")
|
|
406
|
+
return 1
|
|
407
|
+
reg = ToolRegistry.load(home)
|
|
408
|
+
reg.install(Tool(name=name, path=loc["path"], invoke=loc["invoke"],
|
|
409
|
+
source=f"npm:{args.npm}"))
|
|
410
|
+
reg.save(home)
|
|
411
|
+
print(f"installed {name} (npm:{args.npm})")
|
|
412
|
+
return 0
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def cmd_tool_remove(args: argparse.Namespace) -> int:
|
|
416
|
+
import shutil as _sh
|
|
417
|
+
from omega_engine.tools import ToolRegistry
|
|
418
|
+
home = _omega_home()
|
|
419
|
+
reg = ToolRegistry.load(home)
|
|
420
|
+
tool = reg.get(args.name)
|
|
421
|
+
if tool is None:
|
|
422
|
+
print(f"not installed: {args.name}")
|
|
423
|
+
return 1
|
|
424
|
+
tool_dir = home / tool.path
|
|
425
|
+
if tool_dir.exists():
|
|
426
|
+
_sh.rmtree(tool_dir, ignore_errors=True)
|
|
427
|
+
reg.remove(args.name)
|
|
428
|
+
reg.save(home)
|
|
429
|
+
print(f"removed {args.name}")
|
|
430
|
+
return 0
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def cmd_tool_update(args: argparse.Namespace) -> int:
|
|
434
|
+
"""Re-install the tool from its recorded source."""
|
|
435
|
+
from omega_engine.tools import (
|
|
436
|
+
CatalogError,
|
|
437
|
+
Tool,
|
|
438
|
+
ToolRegistry,
|
|
439
|
+
install_from_catalog,
|
|
440
|
+
load_catalog,
|
|
441
|
+
)
|
|
442
|
+
home = _omega_home()
|
|
443
|
+
reg = ToolRegistry.load(home)
|
|
444
|
+
tool = reg.get(args.name)
|
|
445
|
+
if tool is None:
|
|
446
|
+
print(f"not installed: {args.name}")
|
|
447
|
+
return 1
|
|
448
|
+
if tool.source.startswith("mcp:") or tool.source.startswith("catalog:"):
|
|
449
|
+
try:
|
|
450
|
+
catalog = load_catalog(home)
|
|
451
|
+
install_from_catalog(home, args.name, catalog=catalog, registry=reg)
|
|
452
|
+
print(f"updated {args.name}")
|
|
453
|
+
return 0
|
|
454
|
+
except CatalogError as exc:
|
|
455
|
+
print(f"error: {exc}")
|
|
456
|
+
return 1
|
|
457
|
+
if tool.source.startswith("npm:"):
|
|
458
|
+
pkg = tool.source.split(":", 1)[1]
|
|
459
|
+
try:
|
|
460
|
+
loc = _tool_install_npm(args.name, pkg, home)
|
|
461
|
+
except Exception as exc: # noqa: BLE001
|
|
462
|
+
print(f"error: {exc}")
|
|
463
|
+
return 1
|
|
464
|
+
reg.install(Tool(name=args.name, path=loc["path"], invoke=loc["invoke"],
|
|
465
|
+
source=tool.source))
|
|
466
|
+
reg.save(home)
|
|
467
|
+
print(f"updated {args.name}")
|
|
468
|
+
return 0
|
|
469
|
+
print(f"cannot update — unknown source: {tool.source}")
|
|
470
|
+
return 1
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def cmd_tool(args: argparse.Namespace) -> int:
|
|
474
|
+
"""`omega tool` dispatcher."""
|
|
475
|
+
sub = getattr(args, "tool_cmd", None) or "list"
|
|
476
|
+
fns = {
|
|
477
|
+
"list": cmd_tool_list,
|
|
478
|
+
"install": cmd_tool_install,
|
|
479
|
+
"remove": cmd_tool_remove,
|
|
480
|
+
"update": cmd_tool_update,
|
|
481
|
+
}
|
|
482
|
+
if sub not in fns:
|
|
483
|
+
print(f"unknown tool subcommand: {sub}")
|
|
484
|
+
return 2
|
|
485
|
+
return fns[sub](args)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def cmd_sync(_args: argparse.Namespace) -> int:
|
|
489
|
+
"""Project the SSOT into every active provider's native shape."""
|
|
490
|
+
from omega_engine.sync import SyncEngine
|
|
491
|
+
outcomes = SyncEngine().sync_all(_omega_home())
|
|
492
|
+
for o in outcomes:
|
|
493
|
+
if o.get("projected"):
|
|
494
|
+
print(
|
|
495
|
+
f" [ok] {o['provider']}: skills={o['skills_written']} "
|
|
496
|
+
f"commands={o['commands_written']} "
|
|
497
|
+
f"agents={o.get('agents_written', 0)} "
|
|
498
|
+
f"mcp={o['mcp_servers']} hooks={len(o.get('hooks', []))}"
|
|
499
|
+
)
|
|
500
|
+
else:
|
|
501
|
+
print(f" [-] {o['provider']}: {o.get('reason', 'skipped')}")
|
|
502
|
+
return 0
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def cmd_daemon(args: argparse.Namespace) -> int:
|
|
506
|
+
"""Run one of the 24/7 service daemons.
|
|
507
|
+
|
|
508
|
+
``omega daemon engine`` — the event-store keeper + local HTTP API.
|
|
509
|
+
``omega daemon telegram`` — long-polls Telegram, routes inbound messages.
|
|
510
|
+
``omega daemon autonomous`` — runs the autonomous-agent supervisor.
|
|
511
|
+
|
|
512
|
+
The installer's systemd user units invoke these.
|
|
513
|
+
"""
|
|
514
|
+
name = args.name
|
|
515
|
+
if name == "engine":
|
|
516
|
+
from omega_engine.daemons import engine as d
|
|
517
|
+
return d.main()
|
|
518
|
+
if name == "telegram":
|
|
519
|
+
from omega_engine.daemons import telegram as d
|
|
520
|
+
return d.main()
|
|
521
|
+
if name == "autonomous":
|
|
522
|
+
from omega_engine.daemons import autonomous as d
|
|
523
|
+
return d.main()
|
|
524
|
+
print(f"unknown daemon: {name!r} — expected engine|telegram|autonomous")
|
|
525
|
+
return 2
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
138
529
|
parser = argparse.ArgumentParser(prog="omega", description="Omega OS control CLI")
|
|
139
530
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
140
531
|
sub.add_parser("version", help="print the engine version").set_defaults(fn=cmd_version)
|
|
141
532
|
sub.add_parser("doctor", help="validate the deployment").set_defaults(fn=cmd_doctor)
|
|
142
533
|
sub.add_parser("status", help="show all tasks and their derived state").set_defaults(fn=cmd_status)
|
|
143
|
-
|
|
534
|
+
|
|
535
|
+
p_acc = sub.add_parser("account", help="manage the Claude Max account pool")
|
|
536
|
+
p_acc.set_defaults(fn=cmd_account)
|
|
537
|
+
acc_sub = p_acc.add_subparsers(dest="account_cmd")
|
|
538
|
+
acc_sub.add_parser("list", help="show the pool (default)")
|
|
539
|
+
p_login = acc_sub.add_parser("login", help="add an account via OAuth (or manual paste)")
|
|
540
|
+
p_login.add_argument("--id", help="account id (e.g. max-primary)", default=None)
|
|
541
|
+
p_login.add_argument("--label", help="human label", default=None)
|
|
542
|
+
p_login.add_argument("--manual", action="store_true",
|
|
543
|
+
help="skip the device-code attempt; paste a token directly")
|
|
544
|
+
p_use = acc_sub.add_parser("use", help="set an account's status")
|
|
545
|
+
p_use.add_argument("id", help="account id")
|
|
546
|
+
p_use.add_argument("status", choices=["active", "resting", "disabled"])
|
|
547
|
+
p_pool = acc_sub.add_parser("pool", help="edit weights and selection strategy")
|
|
548
|
+
p_pool.add_argument("--selection", choices=["round-robin", "least-used", "by-quota"],
|
|
549
|
+
help="set the pool-wide selection strategy")
|
|
550
|
+
p_pool.add_argument("--weight", action="append", default=[],
|
|
551
|
+
metavar="ID=N",
|
|
552
|
+
help="set an account weight; repeatable")
|
|
553
|
+
|
|
144
554
|
sub.add_parser("billing", help="show usage/cost per Claude Max account").set_defaults(fn=cmd_billing)
|
|
145
555
|
p_run = sub.add_parser("run", help="run a mission end-to-end")
|
|
146
556
|
p_run.add_argument("intent", help="the mission, in natural language")
|
|
@@ -150,6 +560,34 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
150
560
|
p_proj.add_argument("--no-telegram", action="store_true",
|
|
151
561
|
help="skip Telegram topic creation")
|
|
152
562
|
p_proj.set_defaults(fn=cmd_project)
|
|
563
|
+
|
|
564
|
+
p_dae = sub.add_parser("daemon", help="run a 24/7 service daemon")
|
|
565
|
+
p_dae.add_argument("name", choices=["engine", "telegram", "autonomous"],
|
|
566
|
+
help="which daemon to run")
|
|
567
|
+
p_dae.set_defaults(fn=cmd_daemon)
|
|
568
|
+
|
|
569
|
+
p_tool = sub.add_parser("tool", help="manage installed tools (incl. MCP servers)")
|
|
570
|
+
p_tool.set_defaults(fn=cmd_tool)
|
|
571
|
+
tool_sub = p_tool.add_subparsers(dest="tool_cmd")
|
|
572
|
+
tool_sub.add_parser("list", help="list installed tools (default)")
|
|
573
|
+
p_tinst = tool_sub.add_parser("install", help="install from the MCP catalog (default) or via --npm")
|
|
574
|
+
p_tinst.add_argument("name", help="tool name (an MCP catalog id, or a free name when --npm is given)")
|
|
575
|
+
p_tinst.add_argument("--npm", default=None, metavar="PACKAGE",
|
|
576
|
+
help="install a bare npm package under Agentik_Tools/<name>/")
|
|
577
|
+
p_trm = tool_sub.add_parser("remove", help="remove a tool + unregister")
|
|
578
|
+
p_trm.add_argument("name", help="tool name")
|
|
579
|
+
p_tup = tool_sub.add_parser("update", help="re-install a tool from its recorded source")
|
|
580
|
+
p_tup.add_argument("name", help="tool name")
|
|
581
|
+
|
|
582
|
+
sub.add_parser(
|
|
583
|
+
"sync",
|
|
584
|
+
help="project Agentik_SSOT/ into each provider's native shape",
|
|
585
|
+
).set_defaults(fn=cmd_sync)
|
|
586
|
+
return parser
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def main(argv: list[str] | None = None) -> int:
|
|
590
|
+
parser = _build_parser()
|
|
153
591
|
args = parser.parse_args(argv)
|
|
154
592
|
return args.fn(args)
|
|
155
593
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""24/7 service layer for Omega OS.
|
|
2
|
+
|
|
3
|
+
Three long-running daemons backed by systemd user units:
|
|
4
|
+
|
|
5
|
+
* :mod:`omega_engine.daemons.engine` — the event-store keeper + heartbeat
|
|
6
|
+
* :mod:`omega_engine.daemons.telegram` — long-polls Telegram, routes inbound
|
|
7
|
+
messages to either the autonomous supervisor or :func:`run_mission`
|
|
8
|
+
* :mod:`omega_engine.daemons.autonomous` — runs the autonomous-agent
|
|
9
|
+
supervisor
|
|
10
|
+
|
|
11
|
+
Each daemon exposes a ``main()`` entry point invoked by ``omega daemon <name>``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__all__ = ["engine", "telegram", "autonomous"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""The autonomous-agent daemon.
|
|
2
|
+
|
|
3
|
+
Loads every charter under
|
|
4
|
+
``$OMEGA_HOME/Agentik_Orchestration/autonomous/``, builds an
|
|
5
|
+
:class:`omega_engine.autonomous.AutonomousSupervisor`, and runs it until
|
|
6
|
+
SIGTERM/SIGINT.
|
|
7
|
+
|
|
8
|
+
This daemon owns the cron + event triggers. Channel triggers are routed in
|
|
9
|
+
from the Telegram daemon; webhook triggers are awakened by whatever HTTP shim
|
|
10
|
+
your operator put in front (the supervisor exposes ``wake_webhook`` for them).
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
from omega_engine.autonomous import build_supervisor_from_home
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("omega.daemon.autonomous")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> int:
|
|
23
|
+
logging.basicConfig(
|
|
24
|
+
level=os.environ.get("OMEGA_LOG_LEVEL", "INFO"),
|
|
25
|
+
format="%(asctime)s %(name)s %(levelname)s %(message)s",
|
|
26
|
+
)
|
|
27
|
+
# Optional outbound Telegram bridge — used by the supervisor to deliver
|
|
28
|
+
# progress + reports for missions it spawns. Best-effort: missing vault
|
|
29
|
+
# token means "no Telegram", not "abort".
|
|
30
|
+
telegram = None
|
|
31
|
+
try:
|
|
32
|
+
from omega_engine.telegram import TelegramBridge
|
|
33
|
+
telegram = TelegramBridge.from_vault()
|
|
34
|
+
except Exception as exc: # noqa: BLE001
|
|
35
|
+
logger.warning("autonomous daemon: no outbound Telegram (%s)", exc)
|
|
36
|
+
|
|
37
|
+
supervisor = build_supervisor_from_home(telegram=telegram)
|
|
38
|
+
charters = supervisor.charters()
|
|
39
|
+
if not charters:
|
|
40
|
+
logger.warning(
|
|
41
|
+
"autonomous daemon: no charters found — idling. Drop a YAML in "
|
|
42
|
+
"Agentik_Orchestration/autonomous/ and restart."
|
|
43
|
+
)
|
|
44
|
+
else:
|
|
45
|
+
for c in charters:
|
|
46
|
+
logger.info("autonomous: loaded charter %s (%s, trigger=%s)",
|
|
47
|
+
c.id, c.role, c.trigger_type)
|
|
48
|
+
|
|
49
|
+
# blocks until SIGTERM / SIGINT (the supervisor wires both)
|
|
50
|
+
supervisor.run()
|
|
51
|
+
logger.info("autonomous daemon stopped")
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__": # pragma: no cover
|
|
56
|
+
raise SystemExit(main())
|