open-agents-ai 0.103.75 → 0.103.77

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 (2) hide show
  1. package/dist/index.js +185 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19173,7 +19173,8 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
19173
19173
  "skill_build",
19174
19174
  "skill_execute",
19175
19175
  "sub_agent",
19176
- "priority_delegate"
19176
+ "priority_delegate",
19177
+ "ask_user"
19177
19178
  ].includes(tc.name);
19178
19179
  const cachedResult = recentToolResults.get(toolFingerprint);
19179
19180
  if (isReadLike && cachedResult !== void 0) {
@@ -19447,7 +19448,7 @@ Take a DIFFERENT action now.`
19447
19448
  });
19448
19449
  break;
19449
19450
  }
19450
- const narratedToolPattern = /(?:```(?:bash|json|sh)?\s*\n?)?\b(file_read|file_write|file_edit|shell|skill_execute|skill_list|skill_build|grep_search|find_files|web_search|web_fetch|task_complete|memory_read|memory_write|list_directory)\b[\s(=\-]/;
19451
+ const narratedToolPattern = /(?:```(?:bash|json|sh)?\s*\n?)?\b(file_read|file_write|file_edit|shell|skill_execute|skill_list|skill_build|grep_search|find_files|web_search|web_fetch|task_complete|memory_read|memory_write|list_directory|ask_user)\b[\s(=\-]/;
19451
19452
  const narratedMatch = content.match(narratedToolPattern);
19452
19453
  if (narratedMatch) {
19453
19454
  narratedToolCallCount++;
@@ -27119,6 +27120,12 @@ function formatToolArgs(toolName, args, verbose) {
27119
27120
  return `${args["path"] ?? ""}${args["model"] ? ` (${args["model"]})` : ""}`;
27120
27121
  case "transcribe_url":
27121
27122
  return truncStr(String(args["url"] ?? ""), maxArg);
27123
+ case "ask_user": {
27124
+ const q = truncStr(String(args["question"] ?? ""), maxArg - 10);
27125
+ const opts = args["options"];
27126
+ const count = opts ? ` (${opts.length} options)` : "";
27127
+ return `${q}${c2.dim(count)}`;
27128
+ }
27122
27129
  default:
27123
27130
  return Object.entries(args).map(([k, v]) => `${k}=${truncStr(String(v), Math.max(30, maxArg / 3))}`).join(", ");
27124
27131
  }
@@ -27221,7 +27228,9 @@ var init_render = __esm({
27221
27228
  ocr: "\u{1F441}\uFE0F",
27222
27229
  // Transcription tools
27223
27230
  transcribe_file: "\u{1F399}\uFE0F",
27224
- transcribe_url: "\u{1F399}\uFE0F"
27231
+ transcribe_url: "\u{1F399}\uFE0F",
27232
+ // User interaction
27233
+ ask_user: "\u2753"
27225
27234
  };
27226
27235
  TOOL_LABELS = {
27227
27236
  file_read: "Read",
@@ -27255,7 +27264,9 @@ var init_render = __esm({
27255
27264
  ocr: "OCR",
27256
27265
  // Transcription tools
27257
27266
  transcribe_file: "Transcribe",
27258
- transcribe_url: "Transcribe URL"
27267
+ transcribe_url: "Transcribe URL",
27268
+ // User interaction
27269
+ ask_user: "Ask user"
27259
27270
  };
27260
27271
  TOOL_COLORS = {
27261
27272
  file_read: pastel.sky,
@@ -27286,7 +27297,8 @@ var init_render = __esm({
27286
27297
  screenshot: pastel.pink,
27287
27298
  ocr: pastel.pink,
27288
27299
  transcribe_file: pastel.peach,
27289
- transcribe_url: pastel.peach
27300
+ transcribe_url: pastel.peach,
27301
+ ask_user: pastel.sky
27290
27302
  };
27291
27303
  _contentWriteHook = null;
27292
27304
  HINTS = [
@@ -27337,6 +27349,7 @@ var init_render = __esm({
27337
27349
  "aiwg_workflow",
27338
27350
  "create_tool",
27339
27351
  "manage_tools",
27352
+ "ask_user",
27340
27353
  "task_complete"
27341
27354
  ];
27342
27355
  COMMAND_NAMES = [
@@ -30654,13 +30667,43 @@ async function fetchOllamaModels(baseUrl) {
30654
30667
  }
30655
30668
  const data = await resp.json();
30656
30669
  const models = data.models ?? [];
30657
- return models.map((m) => ({
30670
+ const result = models.map((m) => ({
30658
30671
  name: m.name,
30659
30672
  size: formatBytes(m.size),
30660
30673
  sizeBytes: m.size,
30661
30674
  modified: formatRelativeTime(m.modified_at),
30662
- parameterSize: m.details?.parameter_size
30675
+ parameterSize: m.details?.parameter_size,
30676
+ contextLength: void 0
30663
30677
  })).sort((a, b) => b.sizeBytes - a.sizeBytes);
30678
+ const normalized = normalizeBaseUrl(baseUrl);
30679
+ const showResults = await Promise.allSettled(result.map((m) => fetch(`${normalized}/api/show`, {
30680
+ method: "POST",
30681
+ headers: { "Content-Type": "application/json" },
30682
+ body: JSON.stringify({ name: m.name }),
30683
+ signal: AbortSignal.timeout(5e3)
30684
+ }).then((r) => r.ok ? r.json() : null)));
30685
+ for (let i = 0; i < result.length; i++) {
30686
+ const sr = showResults[i];
30687
+ if (sr?.status !== "fulfilled" || !sr.value)
30688
+ continue;
30689
+ const show = sr.value;
30690
+ if (show.parameters) {
30691
+ const match = show.parameters.match(/num_ctx\s+(\d+)/);
30692
+ if (match) {
30693
+ result[i].contextLength = parseInt(match[1], 10);
30694
+ continue;
30695
+ }
30696
+ }
30697
+ if (show.model_info) {
30698
+ for (const [key, value] of Object.entries(show.model_info)) {
30699
+ if (key.endsWith(".context_length") && typeof value === "number") {
30700
+ result[i].contextLength = value;
30701
+ break;
30702
+ }
30703
+ }
30704
+ }
30705
+ }
30706
+ return result;
30664
30707
  }
30665
30708
  async function fetchOpenAIModels(baseUrl, apiKey) {
30666
30709
  const normalized = normalizeBaseUrl(baseUrl);
@@ -30680,10 +30723,11 @@ async function fetchOpenAIModels(baseUrl, apiKey) {
30680
30723
  const models = data.data ?? [];
30681
30724
  return models.map((m) => ({
30682
30725
  name: m.id,
30683
- size: m.context_length ? `${Math.round(m.context_length / 1024)}K ctx` : m.max_model_len ? `${Math.round(m.max_model_len / 1024)}K ctx` : "",
30726
+ size: "",
30684
30727
  sizeBytes: 0,
30685
30728
  modified: m.created ? formatRelativeTime(new Date(m.created * 1e3).toISOString()) : "",
30686
- parameterSize: m.owned_by ?? void 0
30729
+ parameterSize: m.owned_by ?? void 0,
30730
+ contextLength: m.context_length ?? m.max_model_len ?? void 0
30687
30731
  })).sort((a, b) => a.name.localeCompare(b.name));
30688
30732
  }
30689
30733
  async function fetchPeerModels(peerId, authKey) {
@@ -31023,6 +31067,13 @@ function formatBytes(bytes) {
31023
31067
  }
31024
31068
  return `${size.toFixed(1)} ${units[i] ?? "B"}`;
31025
31069
  }
31070
+ function formatContextLength(tokens) {
31071
+ if (tokens >= 1e6)
31072
+ return `${(tokens / 1e6).toFixed(1)}M ctx`;
31073
+ if (tokens >= 1024)
31074
+ return `${Math.round(tokens / 1024)}K ctx`;
31075
+ return `${tokens} ctx`;
31076
+ }
31026
31077
  function formatRelativeTime(iso) {
31027
31078
  const now = Date.now();
31028
31079
  const then = new Date(iso).getTime();
@@ -35148,15 +35199,18 @@ async function showModelPicker(ctx, local = false) {
35148
35199
  const items = [];
35149
35200
  const history = loadUsageHistory("model", ctx.repoRoot);
35150
35201
  const liveModelNames = new Set(models.map((m) => m.name));
35202
+ const modelMap = new Map(models.map((m) => [m.name, m]));
35151
35203
  if (history.length > 0) {
35152
35204
  items.push({ key: "__header_recent__", label: c2.dim("\u2500\u2500 Recent \u2500\u2500"), detail: "" });
35153
35205
  for (const h of history.slice(0, 8)) {
35154
35206
  const uses = h.localUses > 0 ? `${h.useCount} uses (${h.localUses} local)` : `${h.useCount} uses`;
35155
35207
  const available = liveModelNames.has(h.value) ? "" : c2.yellow(" [offline]");
35208
+ const meta = modelMap.get(h.value);
35209
+ const ctx2 = meta?.contextLength ? ` ${formatContextLength(meta.contextLength)}` : "";
35156
35210
  items.push({
35157
35211
  key: h.value,
35158
35212
  label: h.value,
35159
- detail: `${uses}${available}`
35213
+ detail: `${uses}${ctx2}${available}`
35160
35214
  });
35161
35215
  }
35162
35216
  items.push({ key: "__header_available__", label: c2.dim("\u2500\u2500 Available \u2500\u2500"), detail: "" });
@@ -35165,10 +35219,11 @@ async function showModelPicker(ctx, local = false) {
35165
35219
  for (const m of models) {
35166
35220
  if (history.length > 0 && historyKeys.has(m.name))
35167
35221
  continue;
35222
+ const ctx2 = m.contextLength ? formatContextLength(m.contextLength) : "";
35168
35223
  items.push({
35169
35224
  key: m.name,
35170
35225
  label: m.name,
35171
- detail: [m.parameterSize, m.size, m.modified].filter(Boolean).join(" ")
35226
+ detail: [m.parameterSize, ctx2, m.size, m.modified].filter(Boolean).join(" ")
35172
35227
  });
35173
35228
  }
35174
35229
  const result = await tuiSelect({
@@ -46653,7 +46708,7 @@ function formatDMNToolArgs(toolName, args) {
46653
46708
  return "";
46654
46709
  }
46655
46710
  }
46656
- function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine, flowEnabled, slashCommandHandler, thinkingEnabled) {
46711
+ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine, flowEnabled, slashCommandHandler, thinkingEnabled, askUserCallback) {
46657
46712
  const voiceStyleMap = {
46658
46713
  concise: 1,
46659
46714
  balanced: 3,
@@ -46924,6 +46979,62 @@ ${entry.fullContent}`
46924
46979
  }
46925
46980
  });
46926
46981
  }
46982
+ if (askUserCallback) {
46983
+ runner.registerTool({
46984
+ name: "ask_user",
46985
+ description: `Present a decision point to the user with selectable options via an interactive TUI picker. Use this when you need user input to proceed \u2014 choosing between approaches, confirming destructive actions, selecting from a list of alternatives, or any situation where the user's preference matters. For yes/no questions, pass options: ["Yes", "No"]. For multi-choice, pass all available options. The user navigates with arrow keys and selects with Enter. The tool blocks until the user responds.`,
46986
+ parameters: {
46987
+ type: "object",
46988
+ properties: {
46989
+ question: {
46990
+ type: "string",
46991
+ description: "The question or decision to present to the user. Be clear and concise."
46992
+ },
46993
+ options: {
46994
+ type: "array",
46995
+ items: { type: "string" },
46996
+ description: 'Array of selectable options. For binary: ["Yes", "No"]. For multi-choice: ["Option A", "Option B", ...].'
46997
+ },
46998
+ allow_multiple: {
46999
+ type: "boolean",
47000
+ description: "If true, user can select multiple options (space to toggle, Enter to confirm). Default: false."
47001
+ }
47002
+ },
47003
+ required: ["question", "options"]
47004
+ },
47005
+ async execute(args) {
47006
+ const question = String(args.question ?? "");
47007
+ const rawOptions = args.options;
47008
+ const allowMultiple = Boolean(args.allow_multiple);
47009
+ if (!question) {
47010
+ return { success: false, output: "", error: "question is required" };
47011
+ }
47012
+ if (!Array.isArray(rawOptions) || rawOptions.length === 0) {
47013
+ return { success: false, output: "", error: "options must be a non-empty array of strings" };
47014
+ }
47015
+ const options = rawOptions.map((o) => String(o));
47016
+ if (options.length > 20) {
47017
+ return { success: false, output: "", error: "Too many options (max 20). Reduce the choices." };
47018
+ }
47019
+ try {
47020
+ const selected = await askUserCallback(question, options, allowMultiple);
47021
+ if (selected.length === 0) {
47022
+ return { success: true, output: "User cancelled the selection (pressed Escape)." };
47023
+ }
47024
+ return {
47025
+ success: true,
47026
+ output: allowMultiple ? `User selected: ${selected.join(", ")}` : `User selected: ${selected[0]}`
47027
+ };
47028
+ } catch (err) {
47029
+ return {
47030
+ success: false,
47031
+ output: "",
47032
+ error: `Ask user failed: ${err instanceof Error ? err.message : String(err)}`
47033
+ };
47034
+ }
47035
+ }
47036
+ });
47037
+ }
46927
47038
  const filesTouched = /* @__PURE__ */ new Set();
46928
47039
  const toolSequence = [];
46929
47040
  const editSessionId = `task-${Date.now()}`;
@@ -47807,6 +47918,65 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
47807
47918
  const sudoPromptStr = ` ${c2.bold(c2.yellow("\u{1F511} Password:"))} `;
47808
47919
  process.stdout.write(sudoPromptStr);
47809
47920
  }
47921
+ async function handleAskUser(question, options, allowMultiple) {
47922
+ if (statusBar?.isActive)
47923
+ statusBar.beginContentWrite();
47924
+ writeContent(() => {
47925
+ process.stdout.write(`
47926
+ ${c2.bold(c2.cyan("?"))} ${c2.bold(question)}
47927
+ `);
47928
+ });
47929
+ const items = options.map((opt, i) => ({
47930
+ key: String(i),
47931
+ label: opt
47932
+ }));
47933
+ if (allowMultiple) {
47934
+ const selected = /* @__PURE__ */ new Set();
47935
+ const result = await tuiSelect({
47936
+ items,
47937
+ title: "Select one or more (Space to toggle, Enter to confirm)",
47938
+ rl,
47939
+ renderRow: (item, focused, _isActive) => {
47940
+ const isSelected = selected.has(item.key);
47941
+ const marker = isSelected ? c2.green("\u2611") : focused ? c2.cyan("\u2610") : c2.dim("\u2610");
47942
+ const label = focused ? c2.bold(c2.cyan(item.label)) : isSelected ? c2.green(item.label) : item.label;
47943
+ return ` ${marker} ${label}`;
47944
+ },
47945
+ onAction: (item, action) => {
47946
+ if (action === "space") {
47947
+ if (selected.has(item.key)) {
47948
+ selected.delete(item.key);
47949
+ } else {
47950
+ selected.add(item.key);
47951
+ }
47952
+ return true;
47953
+ }
47954
+ return false;
47955
+ }
47956
+ });
47957
+ if (statusBar?.isActive)
47958
+ statusBar.endContentWrite();
47959
+ if (!result.confirmed)
47960
+ return [];
47961
+ if (selected.size === 0 && result.key !== null) {
47962
+ const idx = parseInt(result.key, 10);
47963
+ return [options[idx]];
47964
+ }
47965
+ return Array.from(selected).map((k) => options[parseInt(k, 10)]);
47966
+ } else {
47967
+ const result = await tuiSelect({
47968
+ items,
47969
+ title: "Select one option",
47970
+ rl
47971
+ });
47972
+ if (statusBar?.isActive)
47973
+ statusBar.endContentWrite();
47974
+ if (!result.confirmed || result.key === null)
47975
+ return [];
47976
+ const idx = parseInt(result.key, 10);
47977
+ return [options[idx]];
47978
+ }
47979
+ }
47810
47980
  const evalBackend = {
47811
47981
  async evaluate(prompt) {
47812
47982
  const backend = new OllamaAgenticBackend(currentConfig.backendUrl, currentConfig.model, currentConfig.apiKey);
@@ -49072,7 +49242,7 @@ Execute this skill now. Follow the behavioral guidance above.`;
49072
49242
  }, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary, meta) => {
49073
49243
  lastCompletedSummary = summary;
49074
49244
  lastTaskMeta = meta ?? null;
49075
- }, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine, flowEnabled, buildSlashCommandHandler(), thinkingEnabled);
49245
+ }, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine, flowEnabled, buildSlashCommandHandler(), thinkingEnabled, handleAskUser);
49076
49246
  activeTask = task;
49077
49247
  showPrompt();
49078
49248
  await task.promise;
@@ -49304,7 +49474,7 @@ NEW TASK: ${fullInput}`;
49304
49474
  }, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary, meta) => {
49305
49475
  lastCompletedSummary = summary;
49306
49476
  lastTaskMeta = meta ?? null;
49307
- }, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine, flowEnabled, buildSlashCommandHandler(), thinkingEnabled);
49477
+ }, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine, flowEnabled, buildSlashCommandHandler(), thinkingEnabled, handleAskUser);
49308
49478
  activeTask = task;
49309
49479
  showPrompt();
49310
49480
  await task.promise;
@@ -49574,6 +49744,7 @@ var init_interactive = __esm({
49574
49744
  init_carousel_descriptors();
49575
49745
  init_voice();
49576
49746
  init_stream_renderer();
49747
+ init_tui_select();
49577
49748
  init_edit_history();
49578
49749
  init_dream_engine();
49579
49750
  init_bless_engine();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.75",
3
+ "version": "0.103.77",
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",