open-agents-ai 0.100.0 → 0.101.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
@@ -16927,8 +16927,13 @@ var init_agenticRunner = __esm({
16927
16927
  _fileRegistry = /* @__PURE__ */ new Map();
16928
16928
  // -- Memex Experience Archive (arXiv:2603.04257 inspired) --
16929
16929
  _memexArchive = /* @__PURE__ */ new Map();
16930
- // -- Consecutive ENOENT tracker (prevents path-guessing spirals) --
16930
+ // -- ENOENT tracker (prevents path-guessing spirals) --
16931
+ // Tracks both consecutive AND sliding-window ENOENT counts. The sliding
16932
+ // window catches oscillation patterns (success → ENOENT → success → ENOENT)
16933
+ // that the consecutive counter misses.
16931
16934
  _consecutiveEnoent = 0;
16935
+ _recentEnoents = [];
16936
+ // sliding window of last 8 tool calls
16932
16937
  // -- Session Checkpointing (Priority 5) --
16933
16938
  _sessionId = `session-${Date.now()}`;
16934
16939
  _workingDirectory = "";
@@ -17413,7 +17418,7 @@ Integrate this guidance into your current approach. Continue working on the task
17413
17418
  if (this.aborted)
17414
17419
  return null;
17415
17420
  toolCallCount++;
17416
- const argsKey = Object.keys(tc.arguments ?? {}).sort().join(",");
17421
+ const argsKey = Object.entries(tc.arguments ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${typeof v === "string" ? v.slice(0, 80) : JSON.stringify(v)}`).join(",");
17417
17422
  toolCallLog.push({ name: tc.name, argsKey });
17418
17423
  this.emit({
17419
17424
  type: "tool_call",
@@ -17503,11 +17508,19 @@ ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : resul
17503
17508
  });
17504
17509
  }
17505
17510
  const enoentTools = /* @__PURE__ */ new Set(["file_read", "list_directory", "find_files"]);
17506
- if (!result.success && enoentTools.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(result.error ?? result.output)) {
17511
+ const isEnoent = !result.success && enoentTools.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(result.error ?? result.output);
17512
+ if (enoentTools.has(tc.name)) {
17513
+ this._recentEnoents.push(isEnoent);
17514
+ if (this._recentEnoents.length > 8)
17515
+ this._recentEnoents.shift();
17516
+ }
17517
+ if (isEnoent) {
17507
17518
  this._consecutiveEnoent++;
17508
- if (this._consecutiveEnoent >= 2) {
17519
+ const windowEnoents = this._recentEnoents.filter(Boolean).length;
17520
+ if (this._consecutiveEnoent >= 2 || windowEnoents >= 4) {
17509
17521
  const failedPath = String(tc.arguments?.["path"] ?? tc.arguments?.["file"] ?? "unknown");
17510
- this.pendingUserMessages.push(`[SYSTEM] ${this._consecutiveEnoent} consecutive file-not-found errors (last: "${failedPath}"). You are repeating failed paths. CHANGE YOUR APPROACH:
17522
+ const triggerReason = this._consecutiveEnoent >= 2 ? `${this._consecutiveEnoent} consecutive file-not-found errors` : `${windowEnoents}/8 recent file operations failed`;
17523
+ this.pendingUserMessages.push(`[SYSTEM] ${triggerReason} (last: "${failedPath}"). You are repeating failed paths. CHANGE YOUR APPROACH:
17511
17524
  1. Call list_directory(".") to see what exists at the project root
17512
17525
  2. Entries in a directory listing are RELATIVE to that directory. If you listed "parent/" and saw "child", the full path is "parent/child" \u2014 NOT ".child" or just "child"
17513
17526
  3. If an entry is a directory (marked "d"), use list_directory on it, NOT file_read
@@ -17557,6 +17570,38 @@ ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : resul
17557
17570
  }
17558
17571
  if (completed)
17559
17572
  break;
17573
+ const currentRepScore = this.detectRepetition(toolCallLog);
17574
+ if (currentRepScore > 0.6 && toolCallLog.length >= 6) {
17575
+ const { repetitionWindow } = this.contextLimits();
17576
+ const recentWindow = toolCallLog.slice(-repetitionWindow);
17577
+ const freqMap = /* @__PURE__ */ new Map();
17578
+ for (const tc of recentWindow) {
17579
+ const key = `${tc.name}(${tc.argsKey.slice(0, 60)})`;
17580
+ freqMap.set(key, (freqMap.get(key) ?? 0) + 1);
17581
+ }
17582
+ const topRepeated = [...freqMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 2).map(([k, v]) => `${k} (${v}x)`).join(", ");
17583
+ messages.push({
17584
+ role: "user",
17585
+ content: `[LOOP DETECTED] Your last ${repetitionWindow} tool calls are ${Math.round(currentRepScore * 100)}% repetitive.
17586
+ Repeated calls: ${topRepeated}
17587
+
17588
+ You are stuck. The same call will give the same result. CHANGE YOUR APPROACH:
17589
+ - If exploring: you already have this data. Use it to make a decision.
17590
+ - If looking for a file: use grep_search or find_files instead of listing directories.
17591
+ - If a path doesn't exist: use list_directory(".") to see what does exist.
17592
+ - If confused about the task: re-read the original task prompt above.
17593
+
17594
+ TASK REMINDER: ${this._taskState.goal}
17595
+ ` + (this._taskState.completedSteps.length > 0 ? `Progress so far: ${this._taskState.completedSteps.slice(-3).join("; ")}
17596
+ ` : "") + `
17597
+ Take a DIFFERENT action now.`
17598
+ });
17599
+ this.emit({
17600
+ type: "status",
17601
+ content: `Loop intervention: ${Math.round(currentRepScore * 100)}% repetitive (${topRepeated})`,
17602
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17603
+ });
17604
+ }
17560
17605
  } else {
17561
17606
  const content = msg.content || "";
17562
17607
  messages.push({ role: "assistant", content });
@@ -18106,13 +18151,16 @@ ${tail}`;
18106
18151
  if (memexIndexStr)
18107
18152
  enrichments.push(memexIndexStr);
18108
18153
  const fullSummary = enrichments.join("\n\n");
18154
+ const goalReminder = this._taskState.goal ? `
18155
+
18156
+ **YOUR ACTIVE TASK:** ${this._taskState.goal}` : "";
18109
18157
  const compactionMsg = {
18110
18158
  role: "system",
18111
18159
  content: `[Context compacted${strategyLabel} \u2014 summary of earlier work]
18112
18160
 
18113
18161
  ${fullSummary}
18114
18162
 
18115
- [Continue from the recent context below. Do not repeat work already completed above.]`
18163
+ [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}`
18116
18164
  };
18117
18165
  this.persistCheckpoint(fullSummary);
18118
18166
  let result = [...head, compactionMsg, ...recent];
@@ -29641,17 +29689,41 @@ async function handleSlashCommand(input, ctx) {
29641
29689
  }
29642
29690
  return "handled";
29643
29691
  case "config":
29644
- case "cfg":
29692
+ case "cfg": {
29693
+ const resolved = loadProjectSettings(ctx.repoRoot);
29694
+ const globalS = loadGlobalSettings();
29695
+ const merged = { ...globalS, ...resolved };
29645
29696
  renderConfig({
29697
+ // -- Core inference --
29646
29698
  model: ctx.config.model,
29647
29699
  backendType: ctx.config.backendType,
29648
29700
  backendUrl: ctx.config.backendUrl,
29649
- timeoutMs: String(ctx.config.timeoutMs),
29701
+ apiKey: ctx.config.apiKey ? "[set]" : "[not set]",
29650
29702
  maxRetries: String(ctx.config.maxRetries),
29703
+ timeoutMs: String(ctx.config.timeoutMs),
29704
+ dbPath: ctx.config.dbPath,
29705
+ // -- Behaviour --
29706
+ dryRun: String(ctx.config.dryRun),
29651
29707
  verbose: String(ctx.config.verbose),
29652
- dryRun: String(ctx.config.dryRun)
29708
+ stream: String(merged.stream ?? false),
29709
+ bruteforce: String(merged.bruteforce ?? false),
29710
+ deepContext: String(merged.deepContext ?? false),
29711
+ // -- Presentation --
29712
+ emojis: String(ctx.getEmojis?.() ?? merged.emojis ?? true),
29713
+ colors: String(ctx.getColors?.() ?? merged.colors ?? true),
29714
+ style: String(ctx.getStyle?.() ?? merged.style ?? "balanced"),
29715
+ // -- Voice --
29716
+ voice: String(merged.voice ?? false),
29717
+ voiceModel: String(merged.voiceModel ?? "glados"),
29718
+ // -- Autonomy --
29719
+ commandsMode: String(ctx.getCommandsMode?.() ?? merged.commandsMode ?? "manual"),
29720
+ updateMode: String(merged.updateMode ?? "auto"),
29721
+ // -- Integrations --
29722
+ telegramKey: merged.telegramKey ? "[set]" : "[not set]",
29723
+ telegramAdmin: String(merged.telegramAdmin ?? "[not set]")
29653
29724
  });
29654
29725
  return "handled";
29726
+ }
29655
29727
  case "cost":
29656
29728
  case "costs":
29657
29729
  if (ctx.costTracker) {
@@ -44076,6 +44148,12 @@ function coerceForSettings(key, value) {
44076
44148
  if (BOOL_KEYS.has(key)) {
44077
44149
  return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
44078
44150
  }
44151
+ if (ENUM_KEYS[key]) {
44152
+ const allowed = ENUM_KEYS[key];
44153
+ if (!allowed.includes(value)) {
44154
+ throw new Error(`Invalid value "${value}" for ${key}. Allowed: ${allowed.join(", ")}`);
44155
+ }
44156
+ }
44079
44157
  return value;
44080
44158
  }
44081
44159
  async function configCommand(opts, config) {
@@ -44090,16 +44168,34 @@ async function configCommand(opts, config) {
44090
44168
  function handleShow(opts, config) {
44091
44169
  const repoRoot = resolve30(opts.repoPath ?? cwd3());
44092
44170
  printHeader("Configuration");
44093
- printSection("Active Settings (merged)");
44171
+ const resolved = resolveSettings(repoRoot);
44172
+ printSection("Core Inference");
44094
44173
  printKeyValue("backendUrl", config.backendUrl, 2);
44095
44174
  printKeyValue("backendType", config.backendType, 2);
44096
44175
  printKeyValue("model", config.model, 2);
44097
44176
  printKeyValue("apiKey", config.apiKey ? "[set]" : "[not set]", 2);
44098
44177
  printKeyValue("maxRetries", String(config.maxRetries), 2);
44099
44178
  printKeyValue("timeoutMs", String(config.timeoutMs), 2);
44179
+ printKeyValue("dbPath", config.dbPath, 2);
44180
+ printSection("Behaviour");
44100
44181
  printKeyValue("dryRun", String(config.dryRun), 2);
44101
44182
  printKeyValue("verbose", String(config.verbose), 2);
44102
- printKeyValue("dbPath", config.dbPath, 2);
44183
+ printKeyValue("stream", String(resolved.stream ?? false), 2);
44184
+ printKeyValue("bruteforce", String(resolved.bruteforce ?? false), 2);
44185
+ printKeyValue("deepContext", String(resolved.deepContext ?? false), 2);
44186
+ printSection("Presentation");
44187
+ printKeyValue("emojis", String(resolved.emojis ?? true), 2);
44188
+ printKeyValue("colors", String(resolved.colors ?? true), 2);
44189
+ printKeyValue("style", String(resolved.style ?? "balanced"), 2);
44190
+ printSection("Voice & Audio");
44191
+ printKeyValue("voice", String(resolved.voice ?? false), 2);
44192
+ printKeyValue("voiceModel", String(resolved.voiceModel ?? "glados"), 2);
44193
+ printSection("Agent Autonomy");
44194
+ printKeyValue("commandsMode", String(resolved.commandsMode ?? "manual"), 2);
44195
+ printKeyValue("updateMode", String(resolved.updateMode ?? "auto"), 2);
44196
+ printSection("Integrations");
44197
+ printKeyValue("telegramKey", resolved.telegramKey ? redactIfSensitive("telegramKey", resolved.telegramKey) : "[not set]", 2);
44198
+ printKeyValue("telegramAdmin", String(resolved.telegramAdmin ?? "[not set]"), 2);
44103
44199
  const projectSettings = loadProjectSettings(repoRoot);
44104
44200
  const projectKeys = Object.entries(projectSettings).filter(([, v]) => v !== void 0);
44105
44201
  if (projectKeys.length > 0) {
@@ -44166,10 +44262,28 @@ function handleSet(opts, _config) {
44166
44262
  process.exit(1);
44167
44263
  }
44168
44264
  } else {
44265
+ const AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
44266
+ "backendUrl",
44267
+ "model",
44268
+ "backendType",
44269
+ "apiKey",
44270
+ "maxRetries",
44271
+ "timeoutMs",
44272
+ "dryRun",
44273
+ "verbose",
44274
+ "dbPath"
44275
+ ]);
44169
44276
  try {
44170
- setConfigValue(key, value);
44171
- printSuccess(`Config updated: ${key} = ${redactIfSensitive(key, value)}`);
44172
- printInfo(`Saved to ~/.open-agents/config.json`);
44277
+ const coerced = coerceForSettings(key, value);
44278
+ if (AGENT_CONFIG_KEYS.has(key)) {
44279
+ setConfigValue(key, value);
44280
+ printSuccess(`Config updated: ${key} = ${redactIfSensitive(key, value)}`);
44281
+ printInfo(`Saved to ~/.open-agents/config.json`);
44282
+ } else {
44283
+ saveGlobalSettings({ [key]: coerced });
44284
+ printSuccess(`Setting updated: ${key} = ${redactIfSensitive(key, value)}`);
44285
+ printInfo(`Saved to ~/.open-agents/settings.json`);
44286
+ }
44173
44287
  printInfo("Tip: Use --local to set project-specific overrides.");
44174
44288
  } catch (err) {
44175
44289
  printError(`Failed to save config: ${err instanceof Error ? err.message : String(err)}`);
@@ -44187,7 +44301,7 @@ function handleKeys() {
44187
44301
  printInfo(" oa config set model qwen3.5:122b # global default");
44188
44302
  printInfo(" oa config set model qwen3.5:122b --local # this project only");
44189
44303
  }
44190
- var CONFIG_KEYS, SENSITIVE_KEYS, INT_KEYS, BOOL_KEYS;
44304
+ var CONFIG_KEYS, SENSITIVE_KEYS, INT_KEYS, BOOL_KEYS, ENUM_KEYS;
44191
44305
  var init_config3 = __esm({
44192
44306
  "packages/cli/dist/commands/config.js"() {
44193
44307
  "use strict";
@@ -44195,23 +44309,44 @@ var init_config3 = __esm({
44195
44309
  init_oa_directory();
44196
44310
  init_output();
44197
44311
  CONFIG_KEYS = {
44312
+ // -- Core inference --
44198
44313
  backendUrl: "Backend base URL (Ollama or OpenAI-compatible)",
44199
44314
  backendType: "Backend type: ollama, vllm, fake",
44200
44315
  model: "Model name to use",
44201
44316
  apiKey: "Bearer token for authenticated deployments",
44202
44317
  maxRetries: "Maximum HTTP retries (integer)",
44203
44318
  timeoutMs: "Per-request timeout in milliseconds (integer)",
44319
+ dbPath: "Path to SQLite memory database",
44320
+ // -- Behaviour flags --
44204
44321
  dryRun: "Dry-run mode - patches not written (true/false)",
44205
44322
  verbose: "Verbose output (true/false)",
44206
- dbPath: "Path to SQLite memory database",
44323
+ stream: "Enable real-time token streaming with pastel syntax highlighting (true/false)",
44324
+ bruteforce: "Brute-force mode: auto re-engage agent when turn limit hit (true/false)",
44325
+ deepContext: "Deep context mode - relaxes compaction for complex problem-solving (true/false)",
44326
+ // -- TUI presentation --
44327
+ emojis: "Show emoji icons next to tool calls and status messages (true/false)",
44328
+ colors: "Enable pastel ANSI colors in the TUI (true/false)",
44329
+ style: "Personality preset: concise, balanced, verbose, pedagogical",
44330
+ // -- Voice & audio --
44207
44331
  voice: "Enable TTS voice feedback (true/false)",
44208
44332
  voiceModel: "TTS voice model: glados, overwatch",
44209
- stream: "Enable real-time token streaming with pastel syntax highlighting (true/false)",
44210
- bruteforce: "Brute-force mode: auto re-engage agent when turn limit hit (true/false)"
44333
+ // -- Agent autonomy --
44334
+ commandsMode: "Agent slash-command access: auto (agent can invoke) or manual (user-only)",
44335
+ updateMode: "Update behaviour: auto (after task completion) or manual (/update only)",
44336
+ // -- Integrations --
44337
+ telegramKey: "Telegram bot API token for /telegram bridge",
44338
+ telegramAdmin: "Telegram admin user ID \u2014 only this user can interact with the bot"
44211
44339
  };
44212
- SENSITIVE_KEYS = /* @__PURE__ */ new Set(["apiKey", "api_key", "secret", "password", "token"]);
44340
+ SENSITIVE_KEYS = /* @__PURE__ */ new Set(["apiKey", "api_key", "secret", "password", "token", "telegramKey"]);
44213
44341
  INT_KEYS = /* @__PURE__ */ new Set(["maxRetries", "timeoutMs"]);
44214
- BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream", "bruteforce"]);
44342
+ BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream", "bruteforce", "deepContext", "emojis", "colors"]);
44343
+ ENUM_KEYS = {
44344
+ commandsMode: ["auto", "manual"],
44345
+ updateMode: ["auto", "manual"],
44346
+ style: ["concise", "balanced", "verbose", "pedagogical"],
44347
+ backendType: ["ollama", "vllm", "fake"],
44348
+ voiceModel: ["glados", "overwatch"]
44349
+ };
44215
44350
  }
44216
44351
  });
44217
44352
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.100.0",
3
+ "version": "0.101.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",
@@ -135,8 +135,21 @@ When you discover image files (png, jpg, gif, svg, webp, bmp) during codebase ex
135
135
 
136
136
  ## Self-Awareness & Introspection
137
137
 
138
- You are Open Agent (open-agents-ai), an autonomous AI coding agent. You can discover your own capabilities:
139
-
138
+ You are **Open Agent** (open-agents-ai), an autonomous AI coding agent running on local hardware via Ollama or vLLM with open-weight models. No cloud APIs everything runs on the user's machine.
139
+
140
+ **Core capabilities** (use explore_tools() to unlock more):
141
+ - Code: read, write, edit, search, patch files across any language
142
+ - Shell: run any command — tests, builds, git, npm, docker, etc.
143
+ - Web: search documentation and fetch web pages
144
+ - Memory: persistent cross-session knowledge (memory_read/memory_write)
145
+ - Skills: 250+ behavioral skills (skill_list), build new ones (skill_build)
146
+ - P2P: connect to other agents via nexus (libp2p + NATS mesh)
147
+ - Background tasks: run long commands in background, check status later
148
+ - Desktop/Vision: screenshot, click UI, OCR (via explore_tools)
149
+ - Scheduling: cron jobs, reminders, agenda (via explore_tools)
150
+ - Custom tools: create reusable tools from repeated workflows
151
+
152
+ **Introspection tools** (use to answer questions about yourself):
140
153
  - **Tool discovery**: Use explore_tools() to see all available tools and unlock new ones
141
154
  - **Skill discovery**: Use skill_list() to discover behavioral skills with trigger patterns
142
155
  - **Memory**: Use memory_read/memory_write/memory_search to access persistent cross-session knowledge
@@ -144,9 +157,8 @@ You are Open Agent (open-agents-ai), an autonomous AI coding agent. You can disc
144
157
  - **Metrics**: Use slash_command('stats') to see session performance (when /commands auto)
145
158
  - **Capabilities**: Use slash_command('score') to see hardware inference capabilities (when /commands auto)
146
159
  - **Project map**: Use codebase_map to understand the project structure
147
- - **Self-description**: You run on local hardware via Ollama/vLLM with open-weight models, no cloud APIs
148
160
 
149
- When asked about yourself, use these tools to provide concrete, accurate information rather than generic descriptions.
161
+ When asked "how do you work?" or "what can you do?", answer from the capability list above and use introspection tools for specifics. Do NOT hallucinate capabilities use tools to discover concrete information.
150
162
 
151
163
  ## Project Awareness
152
164
 
@@ -49,4 +49,20 @@ NEVER say "I can't do that". ALWAYS attempt the task using your tools. If a tool
49
49
  - Directory listing entries are RELATIVE to the listed directory. If you list "parent/" and see "child", the full path is "parent/child" — NOT ".child" or just "child"
50
50
  - If an entry is a directory (d), use list_directory on it — NOT file_read
51
51
  - Prefer list_directory over shell ls — it shows full paths ready for your next tool call
52
- - You are Open Agent (open-agents-ai). Use explore_tools() to discover capabilities, skill_list() for skills, memory_read/memory_write for persistent knowledge. When asked about yourself, use tools to provide concrete information.
52
+ ## Self-Awareness
53
+
54
+ You are **Open Agent** (open-agents-ai), an autonomous AI coding agent running on local hardware via Ollama or vLLM with open-weight models. No cloud APIs — everything runs on the user's machine.
55
+
56
+ **Core capabilities** (use explore_tools() to unlock more):
57
+ - Code: read, write, edit, search, patch files across any language
58
+ - Shell: run any command — tests, builds, git, npm, docker, etc.
59
+ - Web: search documentation and fetch web pages
60
+ - Memory: persistent cross-session knowledge (memory_read/memory_write)
61
+ - Skills: 250+ behavioral skills (skill_list), build new ones (skill_build)
62
+ - P2P: connect to other agents via nexus (libp2p + NATS mesh)
63
+ - Background tasks: run long commands in background, check status later
64
+ - Desktop/Vision: screenshot, click UI, OCR (via explore_tools)
65
+ - Scheduling: cron jobs, reminders, agenda (via explore_tools)
66
+ - Custom tools: create reusable tools from repeated workflows
67
+
68
+ When asked "how do you work?" or "what can you do?", answer from this list and use explore_tools() or skill_list() to provide specifics. Do NOT hallucinate capabilities — use tools to discover concrete information.
@@ -19,4 +19,6 @@ Rules:
19
19
  - If ENOENT, list_directory on project root. Don't guess paths.
20
20
  - Directory entries are RELATIVE. If you list "parent/" and see "child", the path is "parent/child" — NOT ".child".
21
21
  - Use list_directory for directories, NOT file_read. Prefer list_directory over shell ls.
22
- - You are Open Agent. Use explore_tools() to discover more tools.
22
+ - You are **Open Agent** (open-agents-ai) an AI coding agent running locally via Ollama/vLLM. No cloud APIs.
23
+ - Core: code editing, shell commands, web search, memory, 250+ skills (skill_list), P2P networking (nexus), background tasks.
24
+ - When asked "what can you do?", use explore_tools() and skill_list() to discover and report your actual capabilities. Do NOT hallucinate.