open-agents-ai 0.46.0 → 0.47.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 CHANGED
@@ -333,6 +333,12 @@ Memory flows bidirectionally: the swarm reads all 5 keys at startup (Phase 0) an
333
333
 
334
334
  The Monitor agent can be "detached" between experiment rounds by the Flow Maintainer. When detached, the monitor receives a sub-task (e.g., "analyze GPU memory patterns from last 3 runs") instead of its standard watch prompt. This lets the swarm use idle monitoring capacity for useful analysis work.
335
335
 
336
+ #### Dependency Management
337
+
338
+ The autoresearch tool uses [`uv`](https://docs.astral.sh/uv/) for zero-setup Python environment management. Running `autoresearch(action="setup")` creates a `pyproject.toml` with all dependencies (torch, kernels, pyarrow, rustbpe, tiktoken, etc.) and runs `uv sync` to create a `.venv` automatically.
339
+
340
+ If the Python scripts are invoked directly (without `uv run`), they self-bootstrap: detect missing packages, create a local `.venv`, install dependencies (including CUDA 12.8 torch), and re-exec with the venv's Python. This handles cases where the agent calls `python3 prepare.py` instead of `uv run prepare.py`.
341
+
336
342
  If no GPU is detected, the REM stage falls back to the standard multi-agent creative exploration (Visionary + Pragmatist + Cross-Pollinator + Synthesizer).
337
343
 
338
344
  ## Blessed Mode — Infinite Warm Loop
@@ -386,6 +392,23 @@ Connect the agent to a Telegram bot. Each incoming message spawns a dedicated su
386
392
 
387
393
  The bot token and admin ID are persisted to project settings, so you only need to set them once. After that, bare `/telegram` toggles the bridge on and off like a service watchdog.
388
394
 
395
+ ### Admin Slash Command Passthrough
396
+
397
+ When the admin sends a `/command` in a private DM, it's routed directly through the terminal's command handler — the same code path as typing the command in the TUI. This means you can control the agent from your phone:
398
+
399
+ ```
400
+ /model qwen3.5:122b → switch model
401
+ /voice → toggle TTS
402
+ /dream → enter dream mode
403
+ /listen → toggle voice input
404
+ /stats → show session metrics
405
+ /config → show current config
406
+ /bless → toggle blessed mode
407
+ /telegram status → check bridge status
408
+ ```
409
+
410
+ The command output is captured, ANSI-stripped, and sent back as a Telegram message. Skill invocations (e.g., `/ralph`, `/eval-agent`) are queued as tasks.
411
+
389
412
  ### Sub-Agent Architecture
390
413
 
391
414
  Each Telegram message spawns an independent `AgenticRunner` sub-agent. Sub-agent tool calls, status updates, and streaming tokens appear in the terminal waterfall view with `✈ @username` prefixes — so you can watch all Telegram conversations happening alongside your main work.
package/dist/index.js CHANGED
@@ -9158,6 +9158,12 @@ Next steps:
9158
9158
  if (!existsSync19(join22(workspace, "train.py"))) {
9159
9159
  return { success: false, output: "", error: `No train.py found in ${workspace}. Run autoresearch(action="setup") first.`, durationMs: Date.now() - start };
9160
9160
  }
9161
+ if (!existsSync19(join22(workspace, ".venv")) && existsSync19(join22(workspace, "pyproject.toml"))) {
9162
+ try {
9163
+ execSync16("uv sync 2>&1", { cwd: workspace, encoding: "utf-8", timeout: 3e5 });
9164
+ } catch {
9165
+ }
9166
+ }
9161
9167
  const timeoutMin = Number(args["timeout_minutes"] ?? 10);
9162
9168
  const timeoutMs = timeoutMin * 60 * 1e3;
9163
9169
  const logPath = join22(workspace, "run.log");
@@ -26629,6 +26635,8 @@ with summary "no_reply" to silently skip without responding.
26629
26635
  onSubAgentEvent = null;
26630
26636
  /** Tool policy config — user overrides from config */
26631
26637
  toolPolicyConfig;
26638
+ /** Command handler for admin DM slash commands (wired from interactive.ts) */
26639
+ commandHandler = null;
26632
26640
  /** Media cache — fileUniqueId → cache entry */
26633
26641
  mediaCache = /* @__PURE__ */ new Map();
26634
26642
  /** Media cache directory */
@@ -26653,6 +26661,15 @@ with summary "no_reply" to silently skip without responding.
26653
26661
  setToolPolicyConfig(config) {
26654
26662
  this.toolPolicyConfig = config;
26655
26663
  }
26664
+ /**
26665
+ * Set a command handler for admin DM slash commands.
26666
+ * When an admin sends a /command in DM, it gets routed through the TUI's
26667
+ * handleSlashCommand instead of spawning a sub-agent.
26668
+ * The handler receives the raw command string and returns a text result.
26669
+ */
26670
+ setCommandHandler(handler) {
26671
+ this.commandHandler = handler;
26672
+ }
26656
26673
  /** Register event handler for sub-agent activity (waterfall view) */
26657
26674
  setOnSubAgentEvent(handler) {
26658
26675
  this.onSubAgentEvent = handler;
@@ -26791,6 +26808,26 @@ with summary "no_reply" to silently skip without responding.
26791
26808
  const isAdmin = this.isAdminUser(msg);
26792
26809
  const toolContext = this.resolveToolContext(msg, isAdmin);
26793
26810
  const isAdminDM = toolContext === "telegram-admin-dm";
26811
+ if (isAdminDM && msg.text.startsWith("/") && this.commandHandler) {
26812
+ const cmdName = msg.text.split(/\s+/)[0].slice(1).toLowerCase();
26813
+ if (cmdName !== "start") {
26814
+ renderTelegramSubAgentEvent(msg.username, `command: ${msg.text}`);
26815
+ try {
26816
+ const result = await this.commandHandler(msg.text);
26817
+ if (result) {
26818
+ const html = convertMarkdownToTelegramHTML(result);
26819
+ await this.sendMessageHTML(msg.chatId, html);
26820
+ } else {
26821
+ await this.sendMessage(msg.chatId, `Command executed: ${msg.text}`);
26822
+ }
26823
+ } catch (err) {
26824
+ const errMsg = err instanceof Error ? err.message : String(err);
26825
+ await this.sendMessage(msg.chatId, `Command error: ${errMsg}`).catch(() => {
26826
+ });
26827
+ }
26828
+ return;
26829
+ }
26830
+ }
26794
26831
  const existing = this.subAgents.get(msg.chatId);
26795
26832
  if (existing && !existing.aborted) {
26796
26833
  existing.runner.injectUserMessage(msg.text);
@@ -29723,6 +29760,36 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
29723
29760
  });
29724
29761
  }
29725
29762
  }
29763
+ telegramBridge.setCommandHandler(async (input) => {
29764
+ const captured = [];
29765
+ const origWrite = process.stdout.write;
29766
+ process.stdout.write = function(chunk, ...args) {
29767
+ if (typeof chunk === "string") {
29768
+ const clean = chunk.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").trim();
29769
+ if (clean)
29770
+ captured.push(clean);
29771
+ }
29772
+ return origWrite.call(process.stdout, chunk, ...args);
29773
+ };
29774
+ try {
29775
+ const result = await handleSlashCommand(input, commandCtx);
29776
+ process.stdout.write = origWrite;
29777
+ if (result === "exit") {
29778
+ return "Exit command received (ignored via Telegram).";
29779
+ }
29780
+ if (result === "not_a_command") {
29781
+ return null;
29782
+ }
29783
+ if (typeof result === "object" && result.type === "skill") {
29784
+ rl.emit("line", input);
29785
+ return `Skill invoked: ${result.name}`;
29786
+ }
29787
+ return captured.length > 0 ? captured.join("\n") : `Done: ${input}`;
29788
+ } catch (err) {
29789
+ process.stdout.write = origWrite;
29790
+ throw err;
29791
+ }
29792
+ });
29726
29793
  await telegramBridge.start();
29727
29794
  writeContent(() => renderTelegramStart(telegramBridge.botUsername, adminId));
29728
29795
  showPrompt();
@@ -3,7 +3,8 @@ One-time data preparation for autoresearch experiments.
3
3
  Downloads data shards and trains a BPE tokenizer.
4
4
 
5
5
  Usage:
6
- python prepare.py # full prep (download + tokenizer)
6
+ uv run prepare.py # recommended (uses pyproject.toml deps)
7
+ python prepare.py # auto-installs missing deps into venv
7
8
  python prepare.py --num-shards 8 # download only 8 shards (for testing)
8
9
 
9
10
  Data and tokenizer are stored in ~/.cache/autoresearch/.
@@ -15,8 +16,77 @@ import time
15
16
  import math
16
17
  import argparse
17
18
  import pickle
19
+ import subprocess
18
20
  from multiprocessing import Pool
19
21
 
22
+ # ---------------------------------------------------------------------------
23
+ # Auto-bootstrap: if running outside uv, ensure deps are installed
24
+ # ---------------------------------------------------------------------------
25
+
26
+ _REQUIRED_PACKAGES = {
27
+ "requests": "requests",
28
+ "pyarrow": "pyarrow",
29
+ "rustbpe": "rustbpe",
30
+ "tiktoken": "tiktoken",
31
+ "torch": "torch",
32
+ }
33
+
34
+ def _bootstrap_deps():
35
+ """Auto-install missing dependencies into a local venv if not using uv."""
36
+ missing = []
37
+ for module_name, pip_name in _REQUIRED_PACKAGES.items():
38
+ try:
39
+ __import__(module_name)
40
+ except ImportError:
41
+ missing.append(pip_name)
42
+
43
+ if not missing:
44
+ return # All deps available
45
+
46
+ print(f"Missing packages: {', '.join(missing)}")
47
+ print("Auto-installing into local venv...")
48
+
49
+ script_dir = os.path.dirname(os.path.abspath(__file__))
50
+ venv_dir = os.path.join(script_dir, ".venv")
51
+
52
+ # Create venv if needed
53
+ if not os.path.exists(venv_dir):
54
+ print(f"Creating venv at {venv_dir}...")
55
+ subprocess.check_call([sys.executable, "-m", "venv", venv_dir])
56
+
57
+ # Determine pip path
58
+ pip_path = os.path.join(venv_dir, "bin", "pip")
59
+ if not os.path.exists(pip_path):
60
+ pip_path = os.path.join(venv_dir, "Scripts", "pip.exe") # Windows
61
+
62
+ # Install missing packages
63
+ # For torch, use the CUDA 12.8 index if available
64
+ torch_pkgs = [p for p in missing if p == "torch"]
65
+ other_pkgs = [p for p in missing if p != "torch"]
66
+
67
+ if other_pkgs:
68
+ print(f"Installing: {', '.join(other_pkgs)}")
69
+ subprocess.check_call([pip_path, "install", "--quiet"] + other_pkgs)
70
+
71
+ if torch_pkgs:
72
+ print("Installing torch (CUDA 12.8)...")
73
+ subprocess.check_call([
74
+ pip_path, "install", "--quiet", "torch",
75
+ "--index-url", "https://download.pytorch.org/whl/cu128",
76
+ ])
77
+
78
+ # Re-exec with the venv's Python
79
+ venv_python = os.path.join(venv_dir, "bin", "python")
80
+ if not os.path.exists(venv_python):
81
+ venv_python = os.path.join(venv_dir, "Scripts", "python.exe")
82
+
83
+ print(f"Re-launching with venv Python: {venv_python}")
84
+ os.execv(venv_python, [venv_python] + sys.argv)
85
+
86
+ # Only bootstrap if not already in a venv/uv-managed environment
87
+ if not (hasattr(sys, "real_prefix") or (hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix)):
88
+ _bootstrap_deps()
89
+
20
90
  import requests
21
91
  import pyarrow.parquet as pq
22
92
  import rustbpe
@@ -5,9 +5,40 @@ Usage: uv run train.py
5
5
  """
6
6
 
7
7
  import os
8
+ import sys
9
+ import subprocess
8
10
  os.environ["PYTORCH_ALLOC_CONF"] = "expandable_segments:True"
9
11
  os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
10
12
 
13
+ # ---------------------------------------------------------------------------
14
+ # Auto-bootstrap: ensure we're running in a venv with deps installed
15
+ # ---------------------------------------------------------------------------
16
+
17
+ def _ensure_venv():
18
+ """If not in a venv, check for .venv in script dir and re-exec."""
19
+ if hasattr(sys, "real_prefix") or (hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix):
20
+ return # Already in a venv
21
+
22
+ script_dir = os.path.dirname(os.path.abspath(__file__))
23
+ venv_python = os.path.join(script_dir, ".venv", "bin", "python")
24
+
25
+ if os.path.exists(venv_python):
26
+ print(f"Re-launching with venv Python: {venv_python}")
27
+ os.execv(venv_python, [venv_python] + sys.argv)
28
+ else:
29
+ # Try to run prepare.py bootstrap first (it creates the venv)
30
+ prepare_py = os.path.join(script_dir, "prepare.py")
31
+ if os.path.exists(prepare_py):
32
+ print("No venv found. Running prepare.py to bootstrap dependencies...")
33
+ subprocess.check_call([sys.executable, prepare_py, "--num-shards", "0"])
34
+ # After prepare.py creates the venv, re-exec
35
+ if os.path.exists(venv_python):
36
+ os.execv(venv_python, [venv_python] + sys.argv)
37
+ print("ERROR: No venv found. Run 'uv sync' or 'python prepare.py' first.", file=sys.stderr)
38
+ sys.exit(1)
39
+
40
+ _ensure_venv()
41
+
11
42
  import gc
12
43
  import math
13
44
  import time
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",