open-agents-ai 0.31.1 → 0.31.3

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 +141 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11012,6 +11012,39 @@ Rules:
11012
11012
  setContextWindowSize(size) {
11013
11013
  this.options.contextWindowSize = size;
11014
11014
  }
11015
+ // -------------------------------------------------------------------------
11016
+ // Context-aware limits — all dynamic values derived from contextWindowSize
11017
+ // and modelTier. Call this instead of using hardcoded magic numbers.
11018
+ // -------------------------------------------------------------------------
11019
+ /**
11020
+ * Compute all context-dependent limits from the current contextWindowSize
11021
+ * and modelTier. Returns sensible defaults when contextWindowSize is 0.
11022
+ */
11023
+ contextLimits() {
11024
+ const ctx = this.options.contextWindowSize;
11025
+ const tier = this.options.modelTier ?? "large";
11026
+ const compactionThreshold = ctx > 0 ? Math.min(this.options.compactionThreshold, Math.floor(ctx * 0.75)) : this.options.compactionThreshold;
11027
+ const keepRecentDivisor = tier === "small" ? 2e3 : tier === "medium" ? 3e3 : 4e3;
11028
+ const keepRecent = ctx > 0 ? Math.max(4, Math.min(12, Math.floor(ctx / keepRecentDivisor))) : 12;
11029
+ const maxOutputTokens = ctx > 0 ? Math.min(this.options.maxTokens, Math.max(2048, Math.floor(ctx * 0.25))) : this.options.maxTokens;
11030
+ const toolOutputMaxChars = ctx > 0 ? Math.max(2e3, Math.min(8e3, Math.floor(ctx * 0.5))) : 8e3;
11031
+ const foldLineThreshold = tier === "small" ? 30 : tier === "medium" ? 35 : 40;
11032
+ const foldHeadLines = tier === "small" ? 12 : tier === "medium" ? 16 : 20;
11033
+ const foldTailLines = tier === "small" ? 5 : tier === "medium" ? 8 : 10;
11034
+ const maxSummaryChars = ctx > 0 ? Math.max(2e3, Math.min(8e3, Math.floor(ctx * 0.2))) : 4e3;
11035
+ const repetitionWindow = tier === "small" ? 6 : tier === "medium" ? 8 : 10;
11036
+ return {
11037
+ compactionThreshold,
11038
+ keepRecent,
11039
+ maxOutputTokens,
11040
+ toolOutputMaxChars,
11041
+ foldLineThreshold,
11042
+ foldHeadLines,
11043
+ foldTailLines,
11044
+ maxSummaryChars,
11045
+ repetitionWindow
11046
+ };
11047
+ }
11015
11048
  /** Register a tool for the agent to use */
11016
11049
  registerTool(tool) {
11017
11050
  this.tools.set(tool.name, tool);
@@ -11093,7 +11126,8 @@ Rules:
11093
11126
  detectRepetition(recentToolCalls) {
11094
11127
  if (recentToolCalls.length < 4)
11095
11128
  return 0;
11096
- const window = recentToolCalls.slice(-8);
11129
+ const { repetitionWindow } = this.contextLimits();
11130
+ const window = recentToolCalls.slice(-repetitionWindow);
11097
11131
  const uniqueKeys = new Set(window.map((tc) => `${tc.name}:${tc.argsKey}`));
11098
11132
  const ratio = 1 - uniqueKeys.size / window.length;
11099
11133
  return ratio;
@@ -11213,8 +11247,7 @@ Integrate this guidance into your current approach. Continue working on the task
11213
11247
  });
11214
11248
  }
11215
11249
  const compacted = this.compactMessages(messages);
11216
- const ctxWindow = this.options.contextWindowSize;
11217
- const effectiveMaxTokens = ctxWindow > 0 ? Math.min(this.options.maxTokens, Math.max(2048, Math.floor(ctxWindow * 0.25))) : this.options.maxTokens;
11250
+ const { maxOutputTokens: effectiveMaxTokens } = this.contextLimits();
11218
11251
  const chatRequest = {
11219
11252
  messages: compacted,
11220
11253
  tools: toolDefs,
@@ -11313,8 +11346,7 @@ Integrate this guidance into your current approach. Continue working on the task
11313
11346
  }
11314
11347
  }
11315
11348
  }
11316
- const ctxW = this.options.contextWindowSize;
11317
- const maxLen = ctxW > 0 ? Math.max(2e3, Math.min(8e3, Math.floor(ctxW * 0.5))) : 8e3;
11349
+ const { toolOutputMaxChars: maxLen } = this.contextLimits();
11318
11350
  const output = result.success ? result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : result.output : `Error: ${result.error || "unknown error"}
11319
11351
  ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : result.output}`;
11320
11352
  this.emit({
@@ -11527,8 +11559,8 @@ Integrate this guidance into your current approach. Continue working on the task
11527
11559
  }
11528
11560
  }
11529
11561
  }
11530
- const maxLen = 8e3;
11531
- const output = result.success ? result.output.length > maxLen ? result.output.slice(0, maxLen) + `
11562
+ const { toolOutputMaxChars: maxLen2 } = this.contextLimits();
11563
+ const output = result.success ? result.output.length > maxLen2 ? result.output.slice(0, maxLen2) + `
11532
11564
  ...(truncated)` : result.output : `Error: ${result.error || "unknown error"}
11533
11565
  ${result.output}`;
11534
11566
  this.emit({ type: "tool_result", toolName: tc.name, content: output.slice(0, 200), success: result.success, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11629,11 +11661,12 @@ ${marker}` : marker);
11629
11661
  // -------------------------------------------------------------------------
11630
11662
  foldOutput(output, maxChars) {
11631
11663
  const lines = output.split("\n");
11632
- if (lines.length <= 40) {
11664
+ const { foldLineThreshold, foldHeadLines, foldTailLines } = this.contextLimits();
11665
+ if (lines.length <= foldLineThreshold) {
11633
11666
  return output.slice(0, maxChars) + "\n...(truncated)";
11634
11667
  }
11635
- const headLines = 20;
11636
- const tailLines = 10;
11668
+ const headLines = foldHeadLines;
11669
+ const tailLines = foldTailLines;
11637
11670
  const head = lines.slice(0, headLines).join("\n");
11638
11671
  const tail = lines.slice(-tailLines).join("\n");
11639
11672
  const omitted = lines.length - headLines - tailLines;
@@ -11662,11 +11695,11 @@ ${tail}`;
11662
11695
  return sum;
11663
11696
  }, 0);
11664
11697
  const estimatedTokens = totalChars / 4;
11665
- if (estimatedTokens < this.options.compactionThreshold) {
11698
+ const limits = this.contextLimits();
11699
+ if (estimatedTokens < limits.compactionThreshold) {
11666
11700
  return messages;
11667
11701
  }
11668
- const ctxWin = this.options.contextWindowSize;
11669
- const keepRecent = ctxWin > 0 ? Math.max(4, Math.min(12, Math.floor(ctxWin / 4e3))) : 12;
11702
+ const keepRecent = limits.keepRecent;
11670
11703
  const head = messages.slice(0, 2);
11671
11704
  if (messages.length <= 2 + keepRecent)
11672
11705
  return messages;
@@ -11720,13 +11753,13 @@ ${combinedSummary}
11720
11753
  * When the combined text exceeds the budget, condense the older summary.
11721
11754
  */
11722
11755
  progressiveSummarize(olderSummary, newerSummary) {
11723
- const MAX_SUMMARY_CHARS = 4e3;
11756
+ const { maxSummaryChars } = this.contextLimits();
11724
11757
  const combined = `${olderSummary}
11725
11758
 
11726
11759
  ---
11727
11760
 
11728
11761
  ${newerSummary}`;
11729
- if (combined.length <= MAX_SUMMARY_CHARS) {
11762
+ if (combined.length <= maxSummaryChars) {
11730
11763
  return combined;
11731
11764
  }
11732
11765
  const condensed = this.condenseSummary(olderSummary);
@@ -11735,8 +11768,8 @@ ${newerSummary}`;
11735
11768
  ---
11736
11769
 
11737
11770
  ${newerSummary}`;
11738
- if (result.length > MAX_SUMMARY_CHARS) {
11739
- const budget = MAX_SUMMARY_CHARS - newerSummary.length - 60;
11771
+ if (result.length > maxSummaryChars) {
11772
+ const budget = maxSummaryChars - newerSummary.length - 60;
11740
11773
  return budget > 200 ? `[Earlier work, condensed]
11741
11774
  ${olderSummary.slice(0, budget)}...
11742
11775
 
@@ -15597,11 +15630,12 @@ async function doSetup(config, rl) {
15597
15630
  const createModelfile = await ask(rl, ` Create optimized model "${c2.bold(customName)}" with ${ctx.label} context? (Y/n) `);
15598
15631
  if (createModelfile.toLowerCase() !== "n") {
15599
15632
  try {
15633
+ const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
15600
15634
  const modelfileContent = [
15601
15635
  `FROM ${selectedVariant.tag}`,
15602
15636
  `PARAMETER num_ctx ${ctx.numCtx}`,
15603
15637
  `PARAMETER temperature 0`,
15604
- `PARAMETER num_predict 16384`,
15638
+ `PARAMETER num_predict ${numPredict}`,
15605
15639
  `PARAMETER stop "<|endoftext|>"`
15606
15640
  ].join("\n");
15607
15641
  const modelDir2 = join24(homedir9(), ".open-agents", "models");
@@ -16003,11 +16037,12 @@ function createExpandedVariant(baseModel, specs, sizeGB) {
16003
16037
  const customName = expandedModelName(baseModel);
16004
16038
  const ctx = calculateContextWindow(specs, sizeGB);
16005
16039
  try {
16040
+ const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
16006
16041
  const modelfileContent = [
16007
16042
  `FROM ${baseModel}`,
16008
16043
  `PARAMETER num_ctx ${ctx.numCtx}`,
16009
16044
  `PARAMETER temperature 0`,
16010
- `PARAMETER num_predict 16384`,
16045
+ `PARAMETER num_predict ${numPredict}`,
16011
16046
  `PARAMETER stop "<|endoftext|>"`
16012
16047
  ].join("\n");
16013
16048
  const modelDir2 = join24(homedir9(), ".open-agents", "models");
@@ -20348,6 +20383,35 @@ var init_status_bar = __esm({
20348
20383
  setInputStateProvider(provider) {
20349
20384
  this.inputStateProvider = provider;
20350
20385
  }
20386
+ /** Sorted list of slash command/skill completions (e.g. ["/help", "/model", ...]) */
20387
+ _completions = [];
20388
+ /**
20389
+ * Set the list of available slash commands and skills for ghost-text autocomplete.
20390
+ * Should include the leading "/" (e.g. "/help", "/model", "/ralph").
20391
+ */
20392
+ setCompletions(completions) {
20393
+ this._completions = completions.slice().sort();
20394
+ }
20395
+ /**
20396
+ * Find the best completion match for current input.
20397
+ * Returns the suffix to show as ghost text, or empty string if no match.
20398
+ * Only shows ghost when cursor is at end of input.
20399
+ */
20400
+ getGhostText(inputLine, cursorPos) {
20401
+ if (!inputLine.startsWith("/") || inputLine.length < 2)
20402
+ return "";
20403
+ if (cursorPos !== void 0 && cursorPos < inputLine.length)
20404
+ return "";
20405
+ if (inputLine.includes(" "))
20406
+ return "";
20407
+ const lower = inputLine.toLowerCase();
20408
+ for (const cmd of this._completions) {
20409
+ if (cmd.toLowerCase().startsWith(lower) && cmd.length > inputLine.length) {
20410
+ return cmd.slice(inputLine.length);
20411
+ }
20412
+ }
20413
+ return "";
20414
+ }
20351
20415
  /** Set recording indicator state (blinking red ●) */
20352
20416
  setRecording(active) {
20353
20417
  this._recording = active;
@@ -20679,9 +20743,11 @@ var init_status_bar = __esm({
20679
20743
  const inputState = this.inputStateProvider?.();
20680
20744
  const fullLine = inputState?.line ?? "";
20681
20745
  const cursorPos = inputState?.cursor ?? 0;
20746
+ const ghost = this.getGhostText(fullLine, cursorPos);
20682
20747
  if (fullLine.length <= availWidth) {
20748
+ const displayLine = ghost ? fullLine + `\x1B[2m\x1B[38;5;240m${ghost}\x1B[0m` : fullLine;
20683
20749
  return {
20684
- lines: [fullLine],
20750
+ lines: [displayLine],
20685
20751
  cursorRow: 0,
20686
20752
  cursorCol: this.promptWidth + cursorPos + 1
20687
20753
  };
@@ -20950,7 +21016,7 @@ function createTaskCompleteTool() {
20950
21016
  }
20951
21017
  };
20952
21018
  }
20953
- function buildTools(repoRoot, config) {
21019
+ function buildTools(repoRoot, config, contextWindowSize) {
20954
21020
  const executionTools = [
20955
21021
  new FileReadTool(repoRoot),
20956
21022
  new FileWriteTool(repoRoot),
@@ -21011,11 +21077,11 @@ function buildTools(repoRoot, config) {
21011
21077
  ];
21012
21078
  return [
21013
21079
  ...executionTools.map(adaptTool2),
21014
- createSubAgentTool(config, repoRoot),
21080
+ createSubAgentTool(config, repoRoot, contextWindowSize),
21015
21081
  createTaskCompleteTool()
21016
21082
  ];
21017
21083
  }
21018
- function createSubAgentTool(config, repoRoot) {
21084
+ function createSubAgentTool(config, repoRoot, ctxWindowSize) {
21019
21085
  return {
21020
21086
  name: "sub_agent",
21021
21087
  description: "Delegate a sub-task to an independent agent with its own context window. Each sub-agent creates an independent backend connection, enabling TRUE PARALLEL inference when the backend supports concurrent requests (Ollama with OLLAMA_NUM_PARALLEL > 1). BEST PRACTICE: Launch multiple sub_agent calls with background=true in ONE response to maximize parallelism. Check results via task_status/task_output.",
@@ -21036,13 +21102,18 @@ function createSubAgentTool(config, repoRoot) {
21036
21102
  return { success: false, output: "", error: "task is required" };
21037
21103
  }
21038
21104
  const backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
21105
+ const subCtxWindow = ctxWindowSize ?? 0;
21106
+ const subTier = getModelTier(config.model);
21107
+ const subCompaction = subTier === "small" ? 12e3 : subTier === "medium" ? 24e3 : 4e4;
21039
21108
  const subRunner = new AgenticRunner(backend, {
21040
21109
  maxTurns,
21041
21110
  maxTokens: 16384,
21042
21111
  temperature: 0,
21043
21112
  requestTimeoutMs: config.timeoutMs,
21044
21113
  taskTimeoutMs: config.timeoutMs * 2,
21045
- compactionThreshold: 4e4
21114
+ compactionThreshold: subCompaction,
21115
+ contextWindowSize: subCtxWindow,
21116
+ modelTier: subTier
21046
21117
  });
21047
21118
  const subTools = [
21048
21119
  new FileReadTool(repoRoot),
@@ -21112,7 +21183,7 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
21112
21183
  // effectively unlimited — no hard timeout, agent runs until complete or aborted
21113
21184
  contextWindowSize: contextWindowSize ?? 0
21114
21185
  });
21115
- const tools = buildTools(repoRoot, config);
21186
+ const tools = buildTools(repoRoot, config, contextWindowSize);
21116
21187
  if (contextWindowSize && contextWindowSize > 0) {
21117
21188
  for (const tool of tools) {
21118
21189
  if ("setContextWindowSize" in tool && typeof tool.setContextWindowSize === "function") {
@@ -21508,14 +21579,58 @@ async function startInteractive(config, repoPath) {
21508
21579
  const idlePrompt = `${c2.bold(c2.white("\u276F "))}`;
21509
21580
  const activePrompt = `${c2.bold(c2.white("+ "))}`;
21510
21581
  const pausedPrompt = `${c2.bold(c2.yellow("| "))}`;
21582
+ const BUILTIN_COMMANDS = [
21583
+ "/help",
21584
+ "/quit",
21585
+ "/exit",
21586
+ "/clear",
21587
+ "/verbose",
21588
+ "/config",
21589
+ "/cost",
21590
+ "/evaluate",
21591
+ "/eval",
21592
+ "/task-type",
21593
+ "/stats",
21594
+ "/metrics",
21595
+ "/dashboard",
21596
+ "/model",
21597
+ "/models",
21598
+ "/endpoint",
21599
+ "/update",
21600
+ "/upgrade",
21601
+ "/voice",
21602
+ "/stream",
21603
+ "/dream",
21604
+ "/listen",
21605
+ "/bruteforce",
21606
+ "/brute",
21607
+ "/emojis",
21608
+ "/colors",
21609
+ "/tools",
21610
+ "/skills",
21611
+ "/pause",
21612
+ "/stop",
21613
+ "/resume"
21614
+ ];
21615
+ const discoveredSkillNames = discoverSkills(repoRoot).map((s) => `/${s.name}`);
21616
+ const allCompletions = [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...discoveredSkillNames])].sort();
21617
+ function completer(line) {
21618
+ if (!line.startsWith("/"))
21619
+ return [[], line];
21620
+ const lower = line.toLowerCase();
21621
+ const hits = allCompletions.filter((c3) => c3.toLowerCase().startsWith(lower));
21622
+ return [hits, line];
21623
+ }
21511
21624
  const rl = readline2.createInterface({
21512
21625
  input: process.stdin,
21513
21626
  output: process.stdout,
21514
21627
  prompt: idlePrompt,
21515
21628
  terminal: true,
21516
- historySize: 100
21629
+ historySize: 100,
21630
+ completer
21517
21631
  });
21518
21632
  statusBar.setPromptText(idlePrompt, 2);
21633
+ statusBar.setCompletions(allCompletions);
21519
21634
  if (statusBar.isActive) {
21520
21635
  rl.output = new Writable({ write: (_c, _e, cb) => cb() });
21521
21636
  statusBar.setInputStateProvider(() => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.31.1",
3
+ "version": "0.31.3",
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",