open-agents-ai 0.100.0 → 0.101.1

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];
@@ -29203,30 +29251,51 @@ function runWithSudo(cmd, password, timeoutMs = 12e4) {
29203
29251
  timeout: timeoutMs,
29204
29252
  env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
29205
29253
  });
29206
- return true;
29207
- } catch {
29208
- return false;
29254
+ return "ok";
29255
+ } catch (err) {
29256
+ const stderr = err?.stderr?.toString() ?? "";
29257
+ if (/incorrect password|authentication failure|Sorry, try again|not in the sudoers/i.test(stderr)) {
29258
+ return "auth_fail";
29259
+ }
29260
+ return "cmd_fail";
29209
29261
  }
29210
29262
  }
29211
- async function sudoInstall(cmd, getSudoPassword, log, cachedPasswordRef, timeoutMs = 12e4) {
29212
- if (cachedPasswordRef.value) {
29213
- if (runWithSudo(cmd, cachedPasswordRef.value, timeoutMs))
29214
- return true;
29263
+ function validateSudoPassword(password) {
29264
+ return runWithSudo("true", password, 1e4) === "ok";
29265
+ }
29266
+ async function acquireSudoPassword(getSudoPassword, log, cachedPasswordRef) {
29267
+ if (cachedPasswordRef.value && validateSudoPassword(cachedPasswordRef.value)) {
29268
+ return cachedPasswordRef.value;
29269
+ }
29270
+ if (trySudoPasswordless("true", 1e4)) {
29271
+ return "";
29215
29272
  }
29273
+ for (let attempt = 0; attempt < 2; attempt++) {
29274
+ if (attempt > 0)
29275
+ log("Authentication failed \u2014 please re-enter password.");
29276
+ const pw = await getSudoPassword();
29277
+ if (!pw)
29278
+ return null;
29279
+ if (validateSudoPassword(pw)) {
29280
+ cachedPasswordRef.value = pw;
29281
+ return pw;
29282
+ }
29283
+ }
29284
+ return null;
29285
+ }
29286
+ async function sudoInstall(cmd, getSudoPassword, log, cachedPasswordRef, timeoutMs = 12e4) {
29216
29287
  if (trySudoPasswordless(cmd, timeoutMs))
29217
29288
  return true;
29218
- const pw = await getSudoPassword();
29219
- if (pw) {
29220
- cachedPasswordRef.value = pw;
29221
- if (runWithSudo(cmd, pw, timeoutMs))
29222
- return true;
29223
- log("Authentication failed \u2014 please re-enter password.");
29224
- const pw2 = await getSudoPassword();
29225
- if (pw2) {
29226
- cachedPasswordRef.value = pw2;
29227
- if (runWithSudo(cmd, pw2, timeoutMs))
29228
- return true;
29229
- }
29289
+ const pw = await acquireSudoPassword(getSudoPassword, log, cachedPasswordRef);
29290
+ if (pw === null)
29291
+ return false;
29292
+ if (pw === "")
29293
+ return false;
29294
+ const result = runWithSudo(cmd, pw, timeoutMs);
29295
+ if (result === "ok")
29296
+ return true;
29297
+ if (result === "cmd_fail") {
29298
+ log(`Install command failed (not an auth issue) \u2014 package may not be available for this OS.`);
29230
29299
  }
29231
29300
  return false;
29232
29301
  }
@@ -29235,108 +29304,68 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29235
29304
  });
29236
29305
  const cachedPasswordRef = { value: null };
29237
29306
  const getPassword = getSudoPassword ?? (() => Promise.resolve(null));
29238
- if (!hasCmd("tesseract")) {
29239
- const pm2 = detectPkgManager();
29240
- if (pm2) {
29241
- const pkgMap = {
29242
- apt: { cmd: "apt-get install -y tesseract-ocr", needsSudo: true },
29243
- dnf: { cmd: "dnf install -y tesseract", needsSudo: true },
29244
- pacman: { cmd: "pacman -S --noconfirm tesseract", needsSudo: true },
29245
- brew: { cmd: "brew install tesseract", needsSudo: false },
29246
- choco: { cmd: "choco install -y tesseract", needsSudo: false }
29247
- };
29248
- const pkg = pkgMap[pm2];
29249
- if (pkg.needsSudo) {
29250
- log("Installing tesseract-ocr...");
29251
- const ok = await sudoInstall(pkg.cmd, getPassword, log, cachedPasswordRef);
29252
- if (ok && hasCmd("tesseract")) {
29253
- log("tesseract-ocr installed successfully.");
29254
- } else {
29255
- log("tesseract-ocr could not be installed \u2014 OCR features will be limited.");
29256
- }
29257
- } else {
29258
- log("Installing tesseract-ocr...");
29259
- try {
29260
- execSync25(pkg.cmd, { stdio: "pipe", timeout: 12e4 });
29261
- if (hasCmd("tesseract")) {
29262
- log("tesseract-ocr installed successfully.");
29263
- } else {
29264
- log("tesseract-ocr install completed but binary not found \u2014 OCR features will be limited.");
29265
- }
29266
- } catch {
29267
- log("tesseract-ocr could not be installed \u2014 OCR features will be limited.");
29268
- }
29269
- }
29270
- } else {
29271
- log("No supported package manager detected \u2014 tesseract OCR unavailable.");
29272
- }
29273
- }
29274
- const pdfDeps = [
29275
- {
29276
- binary: "pdftotext",
29277
- label: "poppler-utils (pdftotext)",
29278
- pkgMap: {
29279
- apt: { cmd: "apt-get install -y poppler-utils", needsSudo: true },
29280
- dnf: { cmd: "dnf install -y poppler-utils", needsSudo: true },
29281
- pacman: { cmd: "pacman -S --noconfirm poppler", needsSudo: true },
29282
- brew: { cmd: "brew install poppler", needsSudo: false },
29283
- choco: { cmd: "choco install -y poppler", needsSudo: false }
29284
- }
29285
- },
29286
- {
29287
- binary: "gs",
29288
- label: "ghostscript",
29289
- pkgMap: {
29290
- apt: { cmd: "apt-get install -y ghostscript", needsSudo: true },
29291
- dnf: { cmd: "dnf install -y ghostscript", needsSudo: true },
29292
- pacman: { cmd: "pacman -S --noconfirm ghostscript", needsSudo: true },
29293
- brew: { cmd: "brew install ghostscript", needsSudo: false },
29294
- choco: { cmd: "choco install -y ghostscript", needsSudo: false }
29295
- }
29296
- },
29297
- {
29298
- binary: "ocrmypdf",
29299
- label: "ocrmypdf",
29300
- pkgMap: {
29301
- apt: { cmd: "apt-get install -y ocrmypdf", needsSudo: true },
29302
- dnf: { cmd: "dnf install -y ocrmypdf", needsSudo: true },
29303
- pacman: { cmd: "pacman -S --noconfirm ocrmypdf", needsSudo: true },
29304
- brew: { cmd: "brew install ocrmypdf", needsSudo: false },
29305
- choco: { cmd: "choco install -y ocrmypdf", needsSudo: false }
29306
- }
29307
- }
29307
+ const allDeps = [
29308
+ { binary: "tesseract", label: "tesseract-ocr", pkgs: { apt: "tesseract-ocr", dnf: "tesseract", pacman: "tesseract", brew: "tesseract", choco: "tesseract" } },
29309
+ { binary: "pdftotext", label: "poppler-utils", pkgs: { apt: "poppler-utils", dnf: "poppler-utils", pacman: "poppler", brew: "poppler", choco: "poppler" } },
29310
+ { binary: "gs", label: "ghostscript", pkgs: { apt: "ghostscript", dnf: "ghostscript", pacman: "ghostscript", brew: "ghostscript", choco: "ghostscript" } },
29311
+ { binary: "ocrmypdf", label: "ocrmypdf", pkgs: { apt: "ocrmypdf", dnf: "ocrmypdf", pacman: "ocrmypdf", brew: "ocrmypdf", choco: "ocrmypdf" } }
29308
29312
  ];
29309
29313
  {
29310
29314
  const pm2 = detectPkgManager();
29311
- for (const dep of pdfDeps) {
29312
- if (hasCmd(dep.binary))
29313
- continue;
29314
- if (!pm2) {
29315
- log(`No supported package manager detected \u2014 ${dep.label} unavailable.`);
29316
- continue;
29317
- }
29318
- const pkg = dep.pkgMap[pm2];
29319
- if (!pkg)
29320
- continue;
29321
- if (pkg.needsSudo) {
29322
- log(`Installing ${dep.label}...`);
29323
- const ok = await sudoInstall(pkg.cmd, getPassword, log, cachedPasswordRef);
29324
- if (ok && hasCmd(dep.binary)) {
29325
- log(`${dep.label} installed successfully.`);
29315
+ const missing = allDeps.filter((d) => !hasCmd(d.binary));
29316
+ if (missing.length === 0) {
29317
+ } else if (!pm2) {
29318
+ for (const d of missing)
29319
+ log(`No supported package manager \u2014 ${d.label} unavailable.`);
29320
+ } else {
29321
+ const pkgNames = missing.map((d) => d.pkgs[pm2]).filter(Boolean);
29322
+ if (pkgNames.length === 0) {
29323
+ for (const d of missing)
29324
+ log(`${d.label} not available for ${pm2}.`);
29325
+ } else {
29326
+ const labels = missing.map((d) => d.label).join(", ");
29327
+ log(`Installing ${labels}...`);
29328
+ const needsSudo = pm2 !== "brew" && pm2 !== "choco";
29329
+ let batchCmd;
29330
+ switch (pm2) {
29331
+ case "apt":
29332
+ batchCmd = `apt-get update -qq && apt-get install -y -qq ${pkgNames.join(" ")}`;
29333
+ break;
29334
+ case "dnf":
29335
+ batchCmd = `dnf install -y -q ${pkgNames.join(" ")}`;
29336
+ break;
29337
+ case "pacman":
29338
+ batchCmd = `pacman -S --noconfirm ${pkgNames.join(" ")}`;
29339
+ break;
29340
+ case "brew":
29341
+ batchCmd = `brew install ${pkgNames.join(" ")}`;
29342
+ break;
29343
+ case "choco":
29344
+ batchCmd = `choco install -y ${pkgNames.join(" ")}`;
29345
+ break;
29346
+ default:
29347
+ batchCmd = `echo "unsupported pm: ${pm2}" && exit 1`;
29348
+ break;
29349
+ }
29350
+ let ok = false;
29351
+ if (needsSudo) {
29352
+ ok = await sudoInstall(batchCmd, getPassword, log, cachedPasswordRef, 18e4);
29326
29353
  } else {
29327
- log(`${dep.label} could not be installed \u2014 PDF OCR features will be limited.`);
29354
+ try {
29355
+ execSync25(batchCmd, { stdio: "pipe", timeout: 18e4 });
29356
+ ok = true;
29357
+ } catch {
29358
+ ok = false;
29359
+ }
29328
29360
  }
29329
- } else {
29330
- log(`Installing ${dep.label}...`);
29331
- try {
29332
- execSync25(pkg.cmd, { stdio: "pipe", timeout: 12e4 });
29333
- if (hasCmd(dep.binary)) {
29334
- log(`${dep.label} installed successfully.`);
29361
+ for (const d of missing) {
29362
+ if (hasCmd(d.binary)) {
29363
+ log(`${d.label} installed.`);
29364
+ } else if (!ok) {
29365
+ log(`${d.label} could not be installed \u2014 features will be limited.`);
29335
29366
  } else {
29336
- log(`${dep.label} install completed but binary not found.`);
29367
+ log(`${d.label} install completed but binary not found.`);
29337
29368
  }
29338
- } catch {
29339
- log(`${dep.label} could not be installed \u2014 PDF OCR features will be limited.`);
29340
29369
  }
29341
29370
  }
29342
29371
  }
@@ -29641,17 +29670,41 @@ async function handleSlashCommand(input, ctx) {
29641
29670
  }
29642
29671
  return "handled";
29643
29672
  case "config":
29644
- case "cfg":
29673
+ case "cfg": {
29674
+ const resolved = loadProjectSettings(ctx.repoRoot);
29675
+ const globalS = loadGlobalSettings();
29676
+ const merged = { ...globalS, ...resolved };
29645
29677
  renderConfig({
29678
+ // -- Core inference --
29646
29679
  model: ctx.config.model,
29647
29680
  backendType: ctx.config.backendType,
29648
29681
  backendUrl: ctx.config.backendUrl,
29649
- timeoutMs: String(ctx.config.timeoutMs),
29682
+ apiKey: ctx.config.apiKey ? "[set]" : "[not set]",
29650
29683
  maxRetries: String(ctx.config.maxRetries),
29684
+ timeoutMs: String(ctx.config.timeoutMs),
29685
+ dbPath: ctx.config.dbPath,
29686
+ // -- Behaviour --
29687
+ dryRun: String(ctx.config.dryRun),
29651
29688
  verbose: String(ctx.config.verbose),
29652
- dryRun: String(ctx.config.dryRun)
29689
+ stream: String(merged.stream ?? false),
29690
+ bruteforce: String(merged.bruteforce ?? false),
29691
+ deepContext: String(merged.deepContext ?? false),
29692
+ // -- Presentation --
29693
+ emojis: String(ctx.getEmojis?.() ?? merged.emojis ?? true),
29694
+ colors: String(ctx.getColors?.() ?? merged.colors ?? true),
29695
+ style: String(ctx.getStyle?.() ?? merged.style ?? "balanced"),
29696
+ // -- Voice --
29697
+ voice: String(merged.voice ?? false),
29698
+ voiceModel: String(merged.voiceModel ?? "glados"),
29699
+ // -- Autonomy --
29700
+ commandsMode: String(ctx.getCommandsMode?.() ?? merged.commandsMode ?? "manual"),
29701
+ updateMode: String(merged.updateMode ?? "auto"),
29702
+ // -- Integrations --
29703
+ telegramKey: merged.telegramKey ? "[set]" : "[not set]",
29704
+ telegramAdmin: String(merged.telegramAdmin ?? "[not set]")
29653
29705
  });
29654
29706
  return "handled";
29707
+ }
29655
29708
  case "cost":
29656
29709
  case "costs":
29657
29710
  if (ctx.costTracker) {
@@ -44076,6 +44129,12 @@ function coerceForSettings(key, value) {
44076
44129
  if (BOOL_KEYS.has(key)) {
44077
44130
  return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
44078
44131
  }
44132
+ if (ENUM_KEYS[key]) {
44133
+ const allowed = ENUM_KEYS[key];
44134
+ if (!allowed.includes(value)) {
44135
+ throw new Error(`Invalid value "${value}" for ${key}. Allowed: ${allowed.join(", ")}`);
44136
+ }
44137
+ }
44079
44138
  return value;
44080
44139
  }
44081
44140
  async function configCommand(opts, config) {
@@ -44090,16 +44149,34 @@ async function configCommand(opts, config) {
44090
44149
  function handleShow(opts, config) {
44091
44150
  const repoRoot = resolve30(opts.repoPath ?? cwd3());
44092
44151
  printHeader("Configuration");
44093
- printSection("Active Settings (merged)");
44152
+ const resolved = resolveSettings(repoRoot);
44153
+ printSection("Core Inference");
44094
44154
  printKeyValue("backendUrl", config.backendUrl, 2);
44095
44155
  printKeyValue("backendType", config.backendType, 2);
44096
44156
  printKeyValue("model", config.model, 2);
44097
44157
  printKeyValue("apiKey", config.apiKey ? "[set]" : "[not set]", 2);
44098
44158
  printKeyValue("maxRetries", String(config.maxRetries), 2);
44099
44159
  printKeyValue("timeoutMs", String(config.timeoutMs), 2);
44160
+ printKeyValue("dbPath", config.dbPath, 2);
44161
+ printSection("Behaviour");
44100
44162
  printKeyValue("dryRun", String(config.dryRun), 2);
44101
44163
  printKeyValue("verbose", String(config.verbose), 2);
44102
- printKeyValue("dbPath", config.dbPath, 2);
44164
+ printKeyValue("stream", String(resolved.stream ?? false), 2);
44165
+ printKeyValue("bruteforce", String(resolved.bruteforce ?? false), 2);
44166
+ printKeyValue("deepContext", String(resolved.deepContext ?? false), 2);
44167
+ printSection("Presentation");
44168
+ printKeyValue("emojis", String(resolved.emojis ?? true), 2);
44169
+ printKeyValue("colors", String(resolved.colors ?? true), 2);
44170
+ printKeyValue("style", String(resolved.style ?? "balanced"), 2);
44171
+ printSection("Voice & Audio");
44172
+ printKeyValue("voice", String(resolved.voice ?? false), 2);
44173
+ printKeyValue("voiceModel", String(resolved.voiceModel ?? "glados"), 2);
44174
+ printSection("Agent Autonomy");
44175
+ printKeyValue("commandsMode", String(resolved.commandsMode ?? "manual"), 2);
44176
+ printKeyValue("updateMode", String(resolved.updateMode ?? "auto"), 2);
44177
+ printSection("Integrations");
44178
+ printKeyValue("telegramKey", resolved.telegramKey ? redactIfSensitive("telegramKey", resolved.telegramKey) : "[not set]", 2);
44179
+ printKeyValue("telegramAdmin", String(resolved.telegramAdmin ?? "[not set]"), 2);
44103
44180
  const projectSettings = loadProjectSettings(repoRoot);
44104
44181
  const projectKeys = Object.entries(projectSettings).filter(([, v]) => v !== void 0);
44105
44182
  if (projectKeys.length > 0) {
@@ -44166,10 +44243,28 @@ function handleSet(opts, _config) {
44166
44243
  process.exit(1);
44167
44244
  }
44168
44245
  } else {
44246
+ const AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
44247
+ "backendUrl",
44248
+ "model",
44249
+ "backendType",
44250
+ "apiKey",
44251
+ "maxRetries",
44252
+ "timeoutMs",
44253
+ "dryRun",
44254
+ "verbose",
44255
+ "dbPath"
44256
+ ]);
44169
44257
  try {
44170
- setConfigValue(key, value);
44171
- printSuccess(`Config updated: ${key} = ${redactIfSensitive(key, value)}`);
44172
- printInfo(`Saved to ~/.open-agents/config.json`);
44258
+ const coerced = coerceForSettings(key, value);
44259
+ if (AGENT_CONFIG_KEYS.has(key)) {
44260
+ setConfigValue(key, value);
44261
+ printSuccess(`Config updated: ${key} = ${redactIfSensitive(key, value)}`);
44262
+ printInfo(`Saved to ~/.open-agents/config.json`);
44263
+ } else {
44264
+ saveGlobalSettings({ [key]: coerced });
44265
+ printSuccess(`Setting updated: ${key} = ${redactIfSensitive(key, value)}`);
44266
+ printInfo(`Saved to ~/.open-agents/settings.json`);
44267
+ }
44173
44268
  printInfo("Tip: Use --local to set project-specific overrides.");
44174
44269
  } catch (err) {
44175
44270
  printError(`Failed to save config: ${err instanceof Error ? err.message : String(err)}`);
@@ -44187,7 +44282,7 @@ function handleKeys() {
44187
44282
  printInfo(" oa config set model qwen3.5:122b # global default");
44188
44283
  printInfo(" oa config set model qwen3.5:122b --local # this project only");
44189
44284
  }
44190
- var CONFIG_KEYS, SENSITIVE_KEYS, INT_KEYS, BOOL_KEYS;
44285
+ var CONFIG_KEYS, SENSITIVE_KEYS, INT_KEYS, BOOL_KEYS, ENUM_KEYS;
44191
44286
  var init_config3 = __esm({
44192
44287
  "packages/cli/dist/commands/config.js"() {
44193
44288
  "use strict";
@@ -44195,23 +44290,44 @@ var init_config3 = __esm({
44195
44290
  init_oa_directory();
44196
44291
  init_output();
44197
44292
  CONFIG_KEYS = {
44293
+ // -- Core inference --
44198
44294
  backendUrl: "Backend base URL (Ollama or OpenAI-compatible)",
44199
44295
  backendType: "Backend type: ollama, vllm, fake",
44200
44296
  model: "Model name to use",
44201
44297
  apiKey: "Bearer token for authenticated deployments",
44202
44298
  maxRetries: "Maximum HTTP retries (integer)",
44203
44299
  timeoutMs: "Per-request timeout in milliseconds (integer)",
44300
+ dbPath: "Path to SQLite memory database",
44301
+ // -- Behaviour flags --
44204
44302
  dryRun: "Dry-run mode - patches not written (true/false)",
44205
44303
  verbose: "Verbose output (true/false)",
44206
- dbPath: "Path to SQLite memory database",
44304
+ stream: "Enable real-time token streaming with pastel syntax highlighting (true/false)",
44305
+ bruteforce: "Brute-force mode: auto re-engage agent when turn limit hit (true/false)",
44306
+ deepContext: "Deep context mode - relaxes compaction for complex problem-solving (true/false)",
44307
+ // -- TUI presentation --
44308
+ emojis: "Show emoji icons next to tool calls and status messages (true/false)",
44309
+ colors: "Enable pastel ANSI colors in the TUI (true/false)",
44310
+ style: "Personality preset: concise, balanced, verbose, pedagogical",
44311
+ // -- Voice & audio --
44207
44312
  voice: "Enable TTS voice feedback (true/false)",
44208
44313
  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)"
44314
+ // -- Agent autonomy --
44315
+ commandsMode: "Agent slash-command access: auto (agent can invoke) or manual (user-only)",
44316
+ updateMode: "Update behaviour: auto (after task completion) or manual (/update only)",
44317
+ // -- Integrations --
44318
+ telegramKey: "Telegram bot API token for /telegram bridge",
44319
+ telegramAdmin: "Telegram admin user ID \u2014 only this user can interact with the bot"
44211
44320
  };
44212
- SENSITIVE_KEYS = /* @__PURE__ */ new Set(["apiKey", "api_key", "secret", "password", "token"]);
44321
+ SENSITIVE_KEYS = /* @__PURE__ */ new Set(["apiKey", "api_key", "secret", "password", "token", "telegramKey"]);
44213
44322
  INT_KEYS = /* @__PURE__ */ new Set(["maxRetries", "timeoutMs"]);
44214
- BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream", "bruteforce"]);
44323
+ BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream", "bruteforce", "deepContext", "emojis", "colors"]);
44324
+ ENUM_KEYS = {
44325
+ commandsMode: ["auto", "manual"],
44326
+ updateMode: ["auto", "manual"],
44327
+ style: ["concise", "balanced", "verbose", "pedagogical"],
44328
+ backendType: ["ollama", "vllm", "fake"],
44329
+ voiceModel: ["glados", "overwatch"]
44330
+ };
44215
44331
  }
44216
44332
  });
44217
44333
 
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.1",
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.