open-agents-ai 0.68.3 → 0.69.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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +234 -38
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -56,6 +56,10 @@ An autonomous multi-turn tool-calling agent that reads your code, makes changes,
56
56
  - **Temporal agency** — schedule future tasks via OS cron, set cross-session reminders, flag attention items — startup injection surfaces due items automatically
57
57
  - **Web crawling** — multi-page web scraping with Crawlee/Playwright for deep documentation extraction
58
58
  - **Task templates** — specialized system prompts and tool recommendations for code, document, analysis, plan tasks
59
+ - **Inference capability scoring** — canirun.ai-style hardware assessment at first launch: memory/compute/speed scores, per-model compatibility matrix, recommended model selection
60
+ - **Auto-install everything** — first-run wizard auto-installs Ollama, curl, Python3, python3-venv with platform-aware package managers (apt, dnf, yum, pacman, apk, zypper, brew)
61
+ - **P2P inference network** — `/p2p` mesh networking with secret-safe variable placeholders (`{{OA_VAR_*}}`), trust tiers (LOCAL/TEE/VERIFIED/PUBLIC), WebSocket peer mesh, and inference routing with automatic secret redaction/injection
62
+ - **Secret vault** — `/secrets` manages API keys and credentials with AES-256-GCM encrypted persistence; secrets are automatically redacted before sending to untrusted inference peers and re-injected on response
59
63
  - **Auto-expanding context** — detects RAM/VRAM and creates an optimized model variant on first run
60
64
  - **Mid-task steering** — type while the agent works to add context without interrupting
61
65
  - **Smart compaction** — 6 context compaction strategies (default, aggressive, decisions, errors, summary, structured) with research-backed design
@@ -839,6 +843,15 @@ The TUI features an animated multilingual phrase carousel, live metrics bar with
839
843
  | `/tools` | List agent-created custom tools |
840
844
  | `/skills [keyword]` | List/search available AIWG skills |
841
845
  | `/<skill-name> [args]` | Invoke an AIWG skill directly |
846
+ | **P2P & Secrets** | |
847
+ | `/p2p start` | Start the P2P inference mesh node |
848
+ | `/p2p connect <url>` | Connect to a remote peer |
849
+ | `/p2p status` | Show mesh status, connected peers, routing stats |
850
+ | `/p2p stop` | Stop the P2P mesh |
851
+ | `/secrets set <name> <value>` | Register a secret in the vault |
852
+ | `/secrets list` | List registered secrets (values hidden) |
853
+ | `/secrets import-env` | Auto-import secrets from environment variables |
854
+ | `/expose` | Expose local inference via cloudflared tunnel |
842
855
  | **Metrics & Updates** | |
843
856
  | `/cost` | Show token cost breakdown for the current session |
844
857
  | `/evaluate` | Score the last completed task with LLM-as-judge |
package/dist/index.js CHANGED
@@ -24906,6 +24906,158 @@ Try manually:
24906
24906
  }
24907
24907
  }
24908
24908
  }
24909
+ function computeInferenceScore(specs) {
24910
+ const effectiveGB = Math.max(specs.gpuVramGB, specs.availableRamGB);
24911
+ const memoryScore = Math.min(100, Math.round(effectiveGB <= 4 ? effectiveGB * 2.5 : effectiveGB <= 16 ? 10 + (effectiveGB - 4) * 2.5 : effectiveGB <= 48 ? 40 + (effectiveGB - 16) * 0.94 : effectiveGB <= 128 ? 70 + (effectiveGB - 48) * 0.375 : 100));
24912
+ let computeScore;
24913
+ if (specs.gpuVramGB >= 24)
24914
+ computeScore = 90;
24915
+ else if (specs.gpuVramGB >= 12)
24916
+ computeScore = 70;
24917
+ else if (specs.gpuVramGB >= 8)
24918
+ computeScore = 55;
24919
+ else if (specs.gpuVramGB >= 4)
24920
+ computeScore = 35;
24921
+ else if (specs.totalRamGB >= 32)
24922
+ computeScore = 25;
24923
+ else
24924
+ computeScore = Math.max(5, Math.round(specs.totalRamGB * 1.5));
24925
+ let speedScore;
24926
+ if (specs.gpuVramGB >= 48)
24927
+ speedScore = 95;
24928
+ else if (specs.gpuVramGB >= 24)
24929
+ speedScore = 80;
24930
+ else if (specs.gpuVramGB >= 12)
24931
+ speedScore = 65;
24932
+ else if (specs.gpuVramGB >= 8)
24933
+ speedScore = 50;
24934
+ else if (specs.gpuVramGB > 0)
24935
+ speedScore = 35;
24936
+ else
24937
+ speedScore = Math.min(30, Math.round(specs.totalRamGB * 0.6));
24938
+ const overall = Math.round(memoryScore * 0.45 + computeScore * 0.35 + speedScore * 0.2);
24939
+ const modelCompat = QWEN_VARIANTS.filter((v) => !v.cloud).map((v) => {
24940
+ const budget = effectiveGB * 0.8;
24941
+ const fits = v.sizeGB <= budget;
24942
+ let note;
24943
+ if (fits) {
24944
+ const headroom = budget - v.sizeGB;
24945
+ note = headroom > v.sizeGB * 0.5 ? "comfortable" : headroom > v.sizeGB * 0.15 ? "good fit" : "tight fit";
24946
+ if (specs.gpuVramGB > 0 && v.sizeGB <= specs.gpuVramGB * 0.8) {
24947
+ note += " (GPU accelerated)";
24948
+ }
24949
+ } else {
24950
+ const overshoot = v.sizeGB - budget;
24951
+ note = `need ${overshoot.toFixed(0)} GB more`;
24952
+ }
24953
+ return { tag: v.tag, fits, note };
24954
+ });
24955
+ let summary;
24956
+ if (overall >= 80)
24957
+ summary = "Excellent \u2014 can run large models with full context windows";
24958
+ else if (overall >= 60)
24959
+ summary = "Strong \u2014 medium to large models with good context";
24960
+ else if (overall >= 40)
24961
+ summary = "Moderate \u2014 small to medium models, reasonable performance";
24962
+ else if (overall >= 20)
24963
+ summary = "Basic \u2014 small models only, limited context";
24964
+ else
24965
+ summary = "Minimal \u2014 very small models or cloud inference recommended";
24966
+ return { overall, memory: memoryScore, compute: computeScore, speed: speedScore, summary, modelCompat };
24967
+ }
24968
+ function renderScoreBar(score, width = 20) {
24969
+ const filled = Math.round(score / 100 * width);
24970
+ const empty = width - filled;
24971
+ const color = score >= 70 ? c2.green : score >= 40 ? c2.yellow : c2.red;
24972
+ return color("\u2588".repeat(filled)) + c2.dim("\u2591".repeat(empty));
24973
+ }
24974
+ function ensurePython3() {
24975
+ process.stdout.write(` ${c2.cyan("\u25CF")} Installing Python3...
24976
+ `);
24977
+ const plat = platform();
24978
+ if (plat === "win32") {
24979
+ process.stdout.write(` ${c2.dim("Download Python from https://python.org/downloads/")}
24980
+
24981
+ `);
24982
+ return;
24983
+ }
24984
+ const strategies = [
24985
+ { check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq python3 python3-venv python3-pip", label: "apt" },
24986
+ { check: "dnf", install: "sudo dnf install -y -q python3 python3-pip", label: "dnf" },
24987
+ { check: "yum", install: "sudo yum install -y -q python3 python3-pip", label: "yum" },
24988
+ { check: "pacman", install: "sudo pacman -S --noconfirm python python-pip", label: "pacman" },
24989
+ { check: "apk", install: "sudo apk add --quiet python3 py3-pip", label: "apk" },
24990
+ { check: "zypper", install: "sudo zypper install -y python3 python3-pip", label: "zypper" }
24991
+ ];
24992
+ if (plat === "darwin") {
24993
+ if (hasCmd("brew")) {
24994
+ try {
24995
+ execSync20("brew install python3", { stdio: "inherit", timeout: 3e5 });
24996
+ if (hasCmd("python3")) {
24997
+ process.stdout.write(` ${c2.green("\u2714")} Python3 installed via Homebrew.
24998
+ `);
24999
+ return;
25000
+ }
25001
+ } catch {
25002
+ }
25003
+ }
25004
+ }
25005
+ for (const s of strategies) {
25006
+ if (hasCmd(s.check)) {
25007
+ try {
25008
+ execSync20(s.install, { stdio: "inherit", timeout: 12e4 });
25009
+ if (hasCmd("python3") || hasCmd("python")) {
25010
+ process.stdout.write(` ${c2.green("\u2714")} Python3 installed via ${s.label}.
25011
+ `);
25012
+ return;
25013
+ }
25014
+ } catch {
25015
+ }
25016
+ }
25017
+ }
25018
+ process.stdout.write(` ${c2.yellow("\u26A0")} Could not install Python3 automatically. Install manually.
25019
+
25020
+ `);
25021
+ }
25022
+ function checkPythonVenv() {
25023
+ try {
25024
+ execSync20("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
25025
+ return true;
25026
+ } catch {
25027
+ try {
25028
+ execSync20("python -m venv --help", { stdio: "pipe", timeout: 5e3 });
25029
+ return true;
25030
+ } catch {
25031
+ return false;
25032
+ }
25033
+ }
25034
+ }
25035
+ function ensurePythonVenv() {
25036
+ process.stdout.write(` ${c2.cyan("\u25CF")} Installing python3-venv...
25037
+ `);
25038
+ const strategies = [
25039
+ { check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq python3-venv", label: "apt" },
25040
+ { check: "dnf", install: "sudo dnf install -y -q python3-libs", label: "dnf" },
25041
+ { check: "pacman", install: "sudo pacman -S --noconfirm python", label: "pacman" },
25042
+ { check: "apk", install: "sudo apk add --quiet python3", label: "apk" }
25043
+ ];
25044
+ for (const s of strategies) {
25045
+ if (hasCmd(s.check)) {
25046
+ try {
25047
+ execSync20(s.install, { stdio: "inherit", timeout: 12e4 });
25048
+ if (checkPythonVenv()) {
25049
+ process.stdout.write(` ${c2.green("\u2714")} python3-venv installed via ${s.label}.
25050
+ `);
25051
+ return;
25052
+ }
25053
+ } catch {
25054
+ }
25055
+ }
25056
+ }
25057
+ process.stdout.write(` ${c2.yellow("\u26A0")} Could not install python3-venv. Install manually: sudo apt install python3-venv
25058
+
25059
+ `);
25060
+ }
24909
25061
  async function runSetupWizard(config) {
24910
25062
  const rl = readline.createInterface({
24911
25063
  input: process.stdin,
@@ -25049,9 +25201,49 @@ async function doSetup(config, rl) {
25049
25201
  `);
25050
25202
  } else {
25051
25203
  process.stdout.write(` ${c2.dim(" GPU:")} No NVIDIA GPU detected (CPU inference)
25204
+ `);
25205
+ }
25206
+ const score = computeInferenceScore(specs);
25207
+ process.stdout.write(`
25208
+ ${c2.bold("Inference Capability Score")}
25209
+ `);
25210
+ process.stdout.write(` ${renderScoreBar(score.overall)} ${c2.bold(String(score.overall))}${c2.dim("/100")}
25211
+ `);
25212
+ process.stdout.write(` ${c2.dim("Memory:")} ${renderScoreBar(score.memory, 15)} ${score.memory} `);
25213
+ process.stdout.write(`${c2.dim("Compute:")} ${renderScoreBar(score.compute, 15)} ${score.compute} `);
25214
+ process.stdout.write(`${c2.dim("Speed:")} ${renderScoreBar(score.speed, 15)} ${score.speed}
25215
+ `);
25216
+ process.stdout.write(` ${c2.dim(score.summary)}
25217
+ `);
25218
+ process.stdout.write(`
25219
+ ${c2.dim("Model compatibility:")}
25220
+ `);
25221
+ for (const compat of score.modelCompat) {
25222
+ const icon = compat.fits ? c2.green("\u2714") : c2.red("\u2716");
25223
+ const tag = compat.fits ? compat.tag : c2.dim(compat.tag);
25224
+ process.stdout.write(` ${icon} ${tag.padEnd(compat.fits ? 20 : 20)} ${c2.dim(compat.note)}
25052
25225
  `);
25053
25226
  }
25054
25227
  process.stdout.write("\n");
25228
+ const hasPython = hasCmd("python3") || hasCmd("python");
25229
+ if (!hasPython) {
25230
+ process.stdout.write(` ${c2.yellow("\u26A0")} Python3 not found (needed for vision, OCR, browser automation).
25231
+ `);
25232
+ const installPy = await ask(rl, ` ${c2.bold("Install Python3?")} (Y/n) `);
25233
+ if (installPy.toLowerCase() !== "n") {
25234
+ ensurePython3();
25235
+ }
25236
+ } else {
25237
+ const hasVenv = checkPythonVenv();
25238
+ if (!hasVenv) {
25239
+ process.stdout.write(` ${c2.yellow("\u26A0")} Python3 venv module not found (needed for Moondream, OCR, browser automation).
25240
+ `);
25241
+ const installVenv = await ask(rl, ` ${c2.bold("Install python3-venv?")} (Y/n) `);
25242
+ if (installVenv.toLowerCase() !== "n") {
25243
+ ensurePythonVenv();
25244
+ }
25245
+ }
25246
+ }
25055
25247
  let models = [];
25056
25248
  let usingCustomEndpoint = false;
25057
25249
  try {
@@ -25060,36 +25252,18 @@ async function doSetup(config, rl) {
25060
25252
  process.stdout.write(` ${c2.yellow("\u26A0")} Cannot reach Ollama at ${c2.bold(config.backendUrl)}
25061
25253
 
25062
25254
  `);
25063
- const ollamaInstalled = hasCmd("ollama");
25064
- if (ollamaInstalled) {
25065
- process.stdout.write(` ${c2.cyan("\u25CF")} Ollama is installed but not running. Starting automatically...
25066
- `);
25067
- try {
25068
- const child = spawn12("ollama", ["serve"], { stdio: "ignore", detached: true });
25069
- child.unref();
25070
- await new Promise((resolve28) => setTimeout(resolve28, 3e3));
25071
- try {
25072
- models = await fetchOllamaModels(config.backendUrl);
25073
- process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
25074
-
25075
- `);
25076
- } catch {
25077
- process.stdout.write(` ${c2.yellow("\u26A0")} Ollama started but not responding yet. It may need a moment.
25078
-
25079
- `);
25080
- }
25081
- } catch {
25082
- process.stdout.write(` ${c2.yellow("\u26A0")} Could not start Ollama. Try running ${c2.bold("ollama serve")} manually.
25083
-
25084
- `);
25255
+ const useCustom = await ask(rl, ` ${c2.bold("Use a custom inference endpoint instead?")} (Y/n) `);
25256
+ if (useCustom.toLowerCase() === "y" || useCustom.toLowerCase() === "yes") {
25257
+ const endpointResult = await promptForCustomEndpoint(config, rl);
25258
+ if (endpointResult) {
25259
+ return endpointResult;
25085
25260
  }
25261
+ usingCustomEndpoint = true;
25086
25262
  } else {
25087
- process.stdout.write(` ${c2.cyan("\u25CF")} Ollama not found. Installing automatically...
25088
- `);
25089
- const installed = await autoInstallOllama(rl);
25090
- if (installed) {
25263
+ const ollamaInstalled = hasCmd("ollama");
25264
+ if (ollamaInstalled) {
25091
25265
  process.stdout.write(`
25092
- ${c2.cyan("\u25CF")} Starting ollama serve...
25266
+ ${c2.cyan("\u25CF")} Ollama is installed but not running. Starting automatically...
25093
25267
  `);
25094
25268
  try {
25095
25269
  const child = spawn12("ollama", ["serve"], { stdio: "ignore", detached: true });
@@ -25101,27 +25275,49 @@ async function doSetup(config, rl) {
25101
25275
 
25102
25276
  `);
25103
25277
  } catch {
25104
- process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed but not responding yet. Try ${c2.bold("ollama serve")} manually.
25278
+ process.stdout.write(` ${c2.yellow("\u26A0")} Ollama started but not responding yet. It may need a moment.
25105
25279
 
25106
25280
  `);
25107
25281
  }
25108
25282
  } catch {
25109
- process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed. Start it with: ${c2.bold("ollama serve")}
25283
+ process.stdout.write(` ${c2.yellow("\u26A0")} Could not start Ollama. Try running ${c2.bold("ollama serve")} manually.
25110
25284
 
25111
25285
  `);
25112
25286
  }
25113
- }
25114
- if (models.length === 0) {
25115
- const useCustom = await ask(rl, ` ${c2.bold("Use a custom inference endpoint instead?")} (y/n) `);
25116
- if (useCustom.toLowerCase() === "y" || useCustom.toLowerCase() === "yes") {
25117
- const endpointResult = await promptForCustomEndpoint(config, rl);
25118
- if (endpointResult) {
25119
- return endpointResult;
25287
+ } else {
25288
+ const installOllama = await ask(rl, `
25289
+ ${c2.bold("Install Ollama for local inference?")} (Y/n) `);
25290
+ if (installOllama.toLowerCase() !== "n") {
25291
+ const installed = await autoInstallOllama(rl);
25292
+ if (installed) {
25293
+ process.stdout.write(`
25294
+ ${c2.cyan("\u25CF")} Starting ollama serve...
25295
+ `);
25296
+ try {
25297
+ const child = spawn12("ollama", ["serve"], { stdio: "ignore", detached: true });
25298
+ child.unref();
25299
+ await new Promise((resolve28) => setTimeout(resolve28, 3e3));
25300
+ try {
25301
+ models = await fetchOllamaModels(config.backendUrl);
25302
+ process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
25303
+
25304
+ `);
25305
+ } catch {
25306
+ process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed but not responding yet. Try ${c2.bold("ollama serve")} manually.
25307
+
25308
+ `);
25309
+ }
25310
+ } catch {
25311
+ process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed. Start it with: ${c2.bold("ollama serve")}
25312
+
25313
+ `);
25314
+ }
25120
25315
  }
25121
- usingCustomEndpoint = true;
25122
25316
  } else {
25123
25317
  process.stdout.write(`
25124
- ${c2.dim("You can configure an endpoint later with /endpoint")}
25318
+ ${c2.dim("You'll need to configure an inference endpoint.")}
25319
+ `);
25320
+ process.stdout.write(` ${c2.dim("Use:")} ${c2.bold("/endpoint <url> --auth <key>")} ${c2.dim("once inside the TUI.")}
25125
25321
 
25126
25322
  `);
25127
25323
  return config.model;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.68.3",
3
+ "version": "0.69.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",
@@ -59,6 +59,7 @@
59
59
  "node": ">=18.0.0"
60
60
  },
61
61
  "dependencies": {
62
+ "aiwg": "^2026.3.2",
62
63
  "better-sqlite3": "^11.7.0",
63
64
  "glob": "^11.0.0",
64
65
  "ignore": "^6.0.2",