open-agents-ai 0.24.0 → 0.25.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/dist/index.js CHANGED
@@ -12684,6 +12684,55 @@ function findModel(models, query) {
12684
12684
  const fuzzy = models.find((m) => m.name.includes(query));
12685
12685
  return fuzzy;
12686
12686
  }
12687
+ async function queryModelContextSize(baseUrl, modelName) {
12688
+ try {
12689
+ const normalized = normalizeBaseUrl(baseUrl);
12690
+ const res = await fetch(`${normalized}/api/show`, {
12691
+ method: "POST",
12692
+ headers: { "Content-Type": "application/json" },
12693
+ body: JSON.stringify({ name: modelName }),
12694
+ signal: AbortSignal.timeout(1e4)
12695
+ });
12696
+ if (!res.ok)
12697
+ return null;
12698
+ const data = await res.json();
12699
+ if (data.parameters) {
12700
+ const match = data.parameters.match(/num_ctx\s+(\d+)/);
12701
+ if (match)
12702
+ return parseInt(match[1], 10);
12703
+ }
12704
+ if (data.model_info) {
12705
+ for (const [key, value] of Object.entries(data.model_info)) {
12706
+ if (key.endsWith(".context_length") && typeof value === "number") {
12707
+ return value;
12708
+ }
12709
+ }
12710
+ }
12711
+ return null;
12712
+ } catch {
12713
+ return null;
12714
+ }
12715
+ }
12716
+ async function queryOpenAIContextSize(baseUrl, modelName, apiKey) {
12717
+ try {
12718
+ const models = await fetchOpenAIModels(baseUrl, apiKey);
12719
+ const model = models.find((m) => m.name === modelName);
12720
+ if (model?.size) {
12721
+ const match = model.size.match(/(\d+)K ctx/);
12722
+ if (match)
12723
+ return parseInt(match[1], 10) * 1024;
12724
+ }
12725
+ return null;
12726
+ } catch {
12727
+ return null;
12728
+ }
12729
+ }
12730
+ async function queryContextSize(baseUrl, modelName, apiKey) {
12731
+ const ollamaSize = await queryModelContextSize(baseUrl, modelName);
12732
+ if (ollamaSize)
12733
+ return ollamaSize;
12734
+ return queryOpenAIContextSize(baseUrl, modelName, apiKey);
12735
+ }
12687
12736
  function formatBytes(bytes) {
12688
12737
  if (bytes < 1024)
12689
12738
  return `${bytes} B`;
@@ -13173,6 +13222,8 @@ function renderSlashHelp() {
13173
13222
  ["/evaluate", "Evaluate last completed task (LLM quality scoring)"],
13174
13223
  ["/task-type", "Set task type (code, document, analysis, plan, general, auto)"],
13175
13224
  ["/stats", "Show session dashboard (metrics, tool usage, task history)"],
13225
+ ["/stop", "Stop current task and save progress (alias: /pause)"],
13226
+ ["/resume", "Resume a previously stopped task"],
13176
13227
  ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
13177
13228
  ["/tools", "List agent-created custom tools"],
13178
13229
  ["/skills", "List available AIWG skills"],
@@ -15097,6 +15148,34 @@ async function handleSlashCommand(input, ctx) {
15097
15148
  renderInfo(`Colors ${next ? "enabled" : "disabled"}.`);
15098
15149
  return "handled";
15099
15150
  }
15151
+ case "stop":
15152
+ case "pause": {
15153
+ if (!ctx.hasActiveTask?.()) {
15154
+ renderWarning("No active task to stop.");
15155
+ return "handled";
15156
+ }
15157
+ const saved = ctx.savePendingTaskState?.() ?? false;
15158
+ const aborted = ctx.abortTask?.() ?? false;
15159
+ if (saved && aborted) {
15160
+ renderInfo("Task stopped and saved. Use /resume to continue later.");
15161
+ } else if (aborted) {
15162
+ renderWarning("Task stopped but state could not be saved.");
15163
+ } else {
15164
+ renderWarning("Could not stop the task.");
15165
+ }
15166
+ return "handled";
15167
+ }
15168
+ case "resume": {
15169
+ if (ctx.hasActiveTask?.()) {
15170
+ renderWarning("A task is already running. Stop it first with /stop.");
15171
+ return "handled";
15172
+ }
15173
+ const resumed = ctx.resumeTask?.() ?? false;
15174
+ if (!resumed) {
15175
+ renderWarning("No saved task to resume.");
15176
+ }
15177
+ return "handled";
15178
+ }
15100
15179
  default: {
15101
15180
  const skills = discoverSkills(ctx.repoRoot);
15102
15181
  const skill = skills.find((s) => s.name === cmd || s.name === cmd.replace(/_/g, "-"));
@@ -15349,6 +15428,12 @@ async function switchModel(query, ctx, local = false) {
15349
15428
  if (local) {
15350
15429
  renderInfo("Saved as project-local override.");
15351
15430
  }
15431
+ if (ctx.setContextWindowSize) {
15432
+ const ctxSize = await queryContextSize(ctx.config.backendUrl, finalModel, ctx.config.apiKey);
15433
+ if (ctxSize) {
15434
+ ctx.setContextWindowSize(ctxSize);
15435
+ }
15436
+ }
15352
15437
  } catch (err) {
15353
15438
  renderError(`Failed to switch model: ${err instanceof Error ? err.message : String(err)}`);
15354
15439
  }
@@ -19337,6 +19422,11 @@ async function startInteractive(config, repoPath) {
19337
19422
  end: () => statusBar.endContentWrite()
19338
19423
  });
19339
19424
  }
19425
+ queryContextSize(config.backendUrl, config.model, config.apiKey).then((ctxSize) => {
19426
+ if (ctxSize)
19427
+ statusBar.setContextWindowSize(ctxSize);
19428
+ }).catch(() => {
19429
+ });
19340
19430
  const provider = detectProvider(config.backendUrl);
19341
19431
  const costTracker = new CostTracker(provider.id);
19342
19432
  const sessionMetrics = new SessionMetrics();
@@ -19530,7 +19620,7 @@ async function startInteractive(config, repoPath) {
19530
19620
  if (lastSubmittedPrompt && activeTask) {
19531
19621
  savePendingTask(repoRoot, {
19532
19622
  prompt: lastSubmittedPrompt,
19533
- progressSummary: `Manual /update triggered. ${sessionToolCallCount} tool calls completed in last task.`,
19623
+ progressSummary: `Task paused by user. ${sessionToolCallCount} tool calls completed.`,
19534
19624
  filesModified: sessionFilesTouched,
19535
19625
  bruteForce: bruteForceEnabled,
19536
19626
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19552,6 +19642,12 @@ async function startInteractive(config, repoPath) {
19552
19642
  writeContent(() => renderToolCallStart(event.toolName ?? "unknown", event.toolArgs ?? {}));
19553
19643
  } else if (event.type === "tool_result") {
19554
19644
  writeContent(() => renderToolResult(event.toolName ?? "unknown", event.success ?? false, event.content ?? ""));
19645
+ } else if (event.type === "token_usage" && event.tokenUsage) {
19646
+ statusBar.updateMetrics({
19647
+ ...event.tokenUsage,
19648
+ estimatedCost: costTracker?.currentCost,
19649
+ hasPricing: costTracker?.hasPricing
19650
+ });
19555
19651
  }
19556
19652
  }).then((state) => {
19557
19653
  writeContent(() => renderDreamEnd(state));
@@ -19628,7 +19724,33 @@ async function startInteractive(config, repoPath) {
19628
19724
  getEmojis: () => getEmojisEnabled(),
19629
19725
  setEmojis: (enabled) => setEmojisEnabled(enabled),
19630
19726
  getColors: () => getColorsEnabled(),
19631
- setColors: (enabled) => setColorsEnabled(enabled)
19727
+ setColors: (enabled) => setColorsEnabled(enabled),
19728
+ setContextWindowSize: (size) => statusBar.setContextWindowSize(size),
19729
+ hasActiveTask: () => activeTask !== null,
19730
+ abortTask() {
19731
+ if (!activeTask)
19732
+ return false;
19733
+ activeTask.runner.abort();
19734
+ writeContent(() => renderInfo("Task aborted."));
19735
+ return true;
19736
+ },
19737
+ resumeTask() {
19738
+ const pendingTask = loadPendingTask(repoRoot);
19739
+ if (!pendingTask)
19740
+ return false;
19741
+ setTimeout(() => {
19742
+ const resumeContext = [
19743
+ `[RESUMED] Original task: ${pendingTask.prompt}`,
19744
+ pendingTask.progressSummary ? `Progress so far: ${pendingTask.progressSummary}` : "",
19745
+ pendingTask.filesModified.length > 0 ? `Files modified before stop: ${pendingTask.filesModified.join(", ")}` : "",
19746
+ `Tool calls completed before stop: ${pendingTask.toolCallCount}`,
19747
+ "Continue where you left off. Do not repeat work already done."
19748
+ ].filter(Boolean).join("\n\n");
19749
+ writeContent(() => renderInfo(`Resuming task: ${pendingTask.prompt.slice(0, 100)}${pendingTask.prompt.length > 100 ? "..." : ""}`));
19750
+ rl.emit("line", resumeContext);
19751
+ }, 100);
19752
+ return true;
19753
+ }
19632
19754
  };
19633
19755
  showPrompt();
19634
19756
  if (hasTaskToResume) {
@@ -20003,6 +20125,7 @@ var init_interactive = __esm({
20003
20125
  init_updater();
20004
20126
  init_commands();
20005
20127
  init_setup();
20128
+ init_model_picker();
20006
20129
  init_project_context();
20007
20130
  init_dist7();
20008
20131
  init_oa_directory();
package/dist/launcher.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // Node version gate — uses only ES5 syntax so it parses on any Node version.
3
- // On old Node: walks through nvm install, then Node 20, then reinstall.
3
+ // On old Node: walks through curl/wget install, nvm install, then Node 20, then reinstall.
4
4
  var nodeVersion = parseInt(process.versions.node, 10);
5
5
 
6
6
  if (nodeVersion < 18) {
@@ -36,6 +36,15 @@ if (nodeVersion < 18) {
36
36
  });
37
37
  }
38
38
 
39
+ function hasCmd(cmd) {
40
+ try {
41
+ childProcess.execSync("which " + cmd, { stdio: "pipe", timeout: 3000 });
42
+ return true;
43
+ } catch (e) {
44
+ return false;
45
+ }
46
+ }
47
+
39
48
  function run(cmd) {
40
49
  console.log(" $ " + cmd);
41
50
  try {
@@ -53,7 +62,58 @@ if (nodeVersion < 18) {
53
62
  process.exit(1);
54
63
  }
55
64
 
56
- function doInstallNvm(next) {
65
+ // Returns "curl -o-" or "wget -qO-" or null
66
+ function getDownloader() {
67
+ if (hasCmd("curl")) return "curl -o-";
68
+ if (hasCmd("wget")) return "wget -qO-";
69
+ return null;
70
+ }
71
+
72
+ // Try to install curl if neither curl nor wget is available
73
+ function ensureDownloader(next) {
74
+ var dl = getDownloader();
75
+ if (dl) { next(dl); return; }
76
+
77
+ console.log(" Neither curl nor wget found.");
78
+ console.log("");
79
+
80
+ ask(" Install curl now? [Y/n] ", function(a) {
81
+ if (a === "n" || a === "no") {
82
+ bail(
83
+ "\n Install manually:\n" +
84
+ " sudo apt install curl (Debian/Ubuntu)\n" +
85
+ " sudo dnf install curl (Fedora)\n" +
86
+ " sudo pacman -S curl (Arch)\n"
87
+ );
88
+ return;
89
+ }
90
+ console.log("");
91
+ var ok = false;
92
+ if (hasCmd("apt-get")) {
93
+ ok = run("sudo -n apt-get install -y curl 2>/dev/null || sudo apt-get install -y curl");
94
+ } else if (hasCmd("dnf")) {
95
+ ok = run("sudo -n dnf install -y curl 2>/dev/null || sudo dnf install -y curl");
96
+ } else if (hasCmd("pacman")) {
97
+ ok = run("sudo -n pacman -S --noconfirm curl 2>/dev/null || sudo pacman -S --noconfirm curl");
98
+ } else if (hasCmd("apk")) {
99
+ ok = run("apk add curl");
100
+ } else {
101
+ console.log(" No supported package manager found (tried apt, dnf, pacman, apk).");
102
+ }
103
+
104
+ if (ok && hasCmd("curl")) {
105
+ console.log("");
106
+ next("curl -o-");
107
+ } else {
108
+ bail(
109
+ "\n Could not install curl. Install it manually, then re-run:\n" +
110
+ " npm i -g open-agents-ai\n"
111
+ );
112
+ }
113
+ });
114
+ }
115
+
116
+ function doInstallNvm(dl, next) {
57
117
  if (hasNvm) {
58
118
  console.log(" nvm found at " + nvmDir);
59
119
  console.log("");
@@ -65,6 +125,7 @@ if (nodeVersion < 18) {
65
125
  bail(
66
126
  "\n Install nvm manually:\n" +
67
127
  " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
128
+ " # or: wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
68
129
  " source ~/.bashrc\n" +
69
130
  " nvm install 20\n" +
70
131
  " npm i -g open-agents-ai\n"
@@ -72,7 +133,7 @@ if (nodeVersion < 18) {
72
133
  return;
73
134
  }
74
135
  console.log("");
75
- var ok = run("curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash");
136
+ var ok = run(dl + " https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash");
76
137
  if (!ok) {
77
138
  bail("\n nvm install failed. See https://github.com/nvm-sh/nvm#installing-and-updating\n");
78
139
  return;
@@ -151,6 +212,7 @@ if (nodeVersion < 18) {
151
212
  bail(
152
213
  "\n To install manually:\n" +
153
214
  " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
215
+ " # or: wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
154
216
  " source ~/.bashrc\n" +
155
217
  " nvm install 20\n" +
156
218
  " nvm alias default 20\n" +
@@ -159,9 +221,12 @@ if (nodeVersion < 18) {
159
221
  return;
160
222
  }
161
223
  console.log("");
162
- doInstallNvm(function() {
163
- doInstallNode(function() {
164
- doReinstall();
224
+ // Ensure curl or wget is available first
225
+ ensureDownloader(function(dl) {
226
+ doInstallNvm(dl, function() {
227
+ doInstallNode(function() {
228
+ doReinstall();
229
+ });
165
230
  });
166
231
  });
167
232
  });
@@ -42,6 +42,15 @@ function ask(question, cb) {
42
42
  });
43
43
  }
44
44
 
45
+ function hasCmd(cmd) {
46
+ try {
47
+ childProcess.execSync("which " + cmd, { stdio: "pipe", timeout: 3000 });
48
+ return true;
49
+ } catch (e) {
50
+ return false;
51
+ }
52
+ }
53
+
45
54
  function run(cmd, opts) {
46
55
  console.log(" $ " + cmd);
47
56
  try {
@@ -58,7 +67,58 @@ function bail(msg) {
58
67
  process.exit(1);
59
68
  }
60
69
 
61
- function doInstallNvm(next) {
70
+ // Returns "curl -o-" or "wget -qO-" or null
71
+ function getDownloader() {
72
+ if (hasCmd("curl")) return "curl -o-";
73
+ if (hasCmd("wget")) return "wget -qO-";
74
+ return null;
75
+ }
76
+
77
+ // Try to install curl if neither curl nor wget is available
78
+ function ensureDownloader(next) {
79
+ var dl = getDownloader();
80
+ if (dl) { next(dl); return; }
81
+
82
+ console.log(" Neither curl nor wget found.");
83
+ console.log("");
84
+
85
+ ask(" Install curl now? [Y/n] ", function(a) {
86
+ if (a === "n" || a === "no") {
87
+ bail(
88
+ "\n Install manually:\n" +
89
+ " sudo apt install curl (Debian/Ubuntu)\n" +
90
+ " sudo dnf install curl (Fedora)\n" +
91
+ " sudo pacman -S curl (Arch)\n"
92
+ );
93
+ return;
94
+ }
95
+ console.log("");
96
+ var ok = false;
97
+ if (hasCmd("apt-get")) {
98
+ ok = run("sudo -n apt-get install -y curl 2>/dev/null || sudo apt-get install -y curl");
99
+ } else if (hasCmd("dnf")) {
100
+ ok = run("sudo -n dnf install -y curl 2>/dev/null || sudo dnf install -y curl");
101
+ } else if (hasCmd("pacman")) {
102
+ ok = run("sudo -n pacman -S --noconfirm curl 2>/dev/null || sudo pacman -S --noconfirm curl");
103
+ } else if (hasCmd("apk")) {
104
+ ok = run("apk add curl");
105
+ } else {
106
+ console.log(" No supported package manager found (tried apt, dnf, pacman, apk).");
107
+ }
108
+
109
+ if (ok && hasCmd("curl")) {
110
+ console.log("");
111
+ next("curl -o-");
112
+ } else {
113
+ bail(
114
+ "\n Could not install curl. Install it manually, then re-run:\n" +
115
+ " npm i -g open-agents-ai\n"
116
+ );
117
+ }
118
+ });
119
+ }
120
+
121
+ function doInstallNvm(dl, next) {
62
122
  if (hasNvm) {
63
123
  console.log(" nvm found at " + nvmDir);
64
124
  console.log("");
@@ -70,6 +130,7 @@ function doInstallNvm(next) {
70
130
  bail(
71
131
  "\n Install nvm manually:\n" +
72
132
  " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
133
+ " # or: wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
73
134
  " source ~/.bashrc\n" +
74
135
  " nvm install 20\n" +
75
136
  " npm i -g open-agents-ai\n"
@@ -77,7 +138,7 @@ function doInstallNvm(next) {
77
138
  return;
78
139
  }
79
140
  console.log("");
80
- var ok = run("curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash");
141
+ var ok = run(dl + " https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash");
81
142
  if (!ok) {
82
143
  bail("\n nvm install failed. See https://github.com/nvm-sh/nvm#installing-and-updating\n");
83
144
  return;
@@ -157,6 +218,7 @@ ask(" Install Node.js 20 now? [Y/n] ", function(a) {
157
218
  bail(
158
219
  "\n To install manually:\n" +
159
220
  " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
221
+ " # or: wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
160
222
  " source ~/.bashrc\n" +
161
223
  " nvm install 20\n" +
162
224
  " nvm alias default 20\n" +
@@ -165,9 +227,12 @@ ask(" Install Node.js 20 now? [Y/n] ", function(a) {
165
227
  return;
166
228
  }
167
229
  console.log("");
168
- doInstallNvm(function() {
169
- doInstallNode(function() {
170
- doReinstall();
230
+ // Ensure curl or wget is available first
231
+ ensureDownloader(function(dl) {
232
+ doInstallNvm(dl, function() {
233
+ doInstallNode(function() {
234
+ doReinstall();
235
+ });
171
236
  });
172
237
  });
173
238
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.24.0",
3
+ "version": "0.25.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",