open-agents-ai 0.10.8 → 0.11.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.
Files changed (2) hide show
  1. package/dist/index.js +200 -80
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1854,15 +1854,26 @@ var init_memory_read = __esm({
1854
1854
  durationMs: performance.now() - start
1855
1855
  };
1856
1856
  }
1857
+ const entry = entries[key];
1858
+ const artifactTag = entry?.artifactId ? ` [artifact: ${entry.artifactId}]` : "";
1857
1859
  return {
1858
1860
  success: true,
1859
- output: JSON.stringify(entries[key], null, 2),
1861
+ output: JSON.stringify(entry, null, 2) + artifactTag,
1860
1862
  durationMs: performance.now() - start
1861
1863
  };
1862
1864
  }
1865
+ const keys = Object.keys(entries);
1866
+ const summary = keys.map((k) => {
1867
+ const e = entries[k];
1868
+ const aid = e?.artifactId ? ` [${e.artifactId}]` : "";
1869
+ return ` ${k}${aid}`;
1870
+ }).join("\n");
1863
1871
  return {
1864
1872
  success: true,
1865
- output: JSON.stringify(entries, null, 2),
1873
+ output: `Topic: ${topic} (${keys.length} entries)
1874
+ ${summary}
1875
+
1876
+ ${JSON.stringify(entries, null, 2)}`,
1866
1877
  durationMs: performance.now() - start
1867
1878
  };
1868
1879
  } catch (error) {
@@ -1881,6 +1892,7 @@ var init_memory_read = __esm({
1881
1892
  // packages/execution/dist/tools/memory-write.js
1882
1893
  import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir2 } from "node:fs/promises";
1883
1894
  import { resolve as resolve7, join as join4 } from "node:path";
1895
+ import { randomBytes } from "node:crypto";
1884
1896
  var MemoryWriteTool;
1885
1897
  var init_memory_write = __esm({
1886
1898
  "packages/execution/dist/tools/memory-write.js"() {
@@ -1927,15 +1939,32 @@ var init_memory_write = __esm({
1927
1939
  entries = JSON.parse(raw);
1928
1940
  } catch {
1929
1941
  }
1942
+ const artifactId = `mem-${randomBytes(6).toString("hex")}`;
1930
1943
  entries[key] = {
1931
1944
  value,
1932
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1945
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1946
+ artifactId
1933
1947
  };
1934
1948
  await writeFile3(topicFile, JSON.stringify(entries, null, 2), "utf-8");
1935
1949
  }
1950
+ const provenanceDir = resolve7(this.workingDir, ".oa", "provenance");
1951
+ try {
1952
+ await mkdir2(provenanceDir, { recursive: true });
1953
+ const provRecord = {
1954
+ type: "memory_write",
1955
+ topic,
1956
+ key,
1957
+ artifactId: `mem-${randomBytes(6).toString("hex")}`,
1958
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1959
+ source: "agent"
1960
+ };
1961
+ const provFile = join4(provenanceDir, `${Date.now()}-memory-write.json`);
1962
+ await writeFile3(provFile, JSON.stringify(provRecord, null, 2), "utf-8");
1963
+ } catch {
1964
+ }
1936
1965
  return {
1937
1966
  success: true,
1938
- output: `Stored memory: ${topic}.${key}`,
1967
+ output: `Stored memory: ${topic}.${key} [artifact: mem-${Date.now().toString(36)}]`,
1939
1968
  durationMs: performance.now() - start
1940
1969
  };
1941
1970
  } catch (error) {
@@ -7491,6 +7520,7 @@ async function handleSlashCommand(input, ctx) {
7491
7520
  case "verbose":
7492
7521
  case "v":
7493
7522
  ctx.setVerbose(!ctx.config.verbose);
7523
+ ctx.saveSettings({ verbose: ctx.config.verbose });
7494
7524
  renderInfo(`Verbose mode: ${ctx.config.verbose ? "on" : "off"}`);
7495
7525
  return "handled";
7496
7526
  case "config":
@@ -7526,9 +7556,12 @@ async function handleSlashCommand(input, ctx) {
7526
7556
  case "voice": {
7527
7557
  if (arg) {
7528
7558
  const msg = await ctx.voiceSetModel(arg);
7559
+ ctx.saveSettings({ voice: true, voiceModel: arg });
7529
7560
  renderInfo(msg);
7530
7561
  } else {
7531
7562
  const msg = await ctx.voiceToggle();
7563
+ const isOn = msg.toLowerCase().includes("enabled") || msg.toLowerCase().includes("on");
7564
+ ctx.saveSettings({ voice: isOn });
7532
7565
  renderInfo(msg);
7533
7566
  }
7534
7567
  return "handled";
@@ -7626,6 +7659,7 @@ async function handleEndpoint(arg, ctx) {
7626
7659
  if (apiKey) {
7627
7660
  setConfigValue("apiKey", apiKey);
7628
7661
  }
7662
+ ctx.saveSettings({ backendUrl: url, backendType, ...apiKey ? { apiKey } : {} });
7629
7663
  process.stdout.write(`
7630
7664
  ${c2.green("\u2714")} Endpoint updated and saved:
7631
7665
  `);
@@ -7707,6 +7741,7 @@ async function switchModel(query, ctx) {
7707
7741
  }
7708
7742
  const oldModel = ctx.config.model;
7709
7743
  ctx.setModel(match.name);
7744
+ ctx.saveSettings({ model: match.name });
7710
7745
  renderModelSwitch(oldModel, match.name);
7711
7746
  } catch (err) {
7712
7747
  renderError(`Failed to switch model: ${err instanceof Error ? err.message : String(err)}`);
@@ -8112,6 +8147,7 @@ var init_setup = __esm({
8112
8147
  // packages/cli/dist/tui/oa-directory.js
8113
8148
  import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync as readdirSync4, statSync as statSync5 } from "node:fs";
8114
8149
  import { join as join14, relative as relative2, basename as basename2, extname as extname4 } from "node:path";
8150
+ import { homedir as homedir4 } from "node:os";
8115
8151
  function initOaDirectory(repoRoot) {
8116
8152
  const oaPath = join14(repoRoot, OA_DIR);
8117
8153
  for (const sub of SUBDIRS) {
@@ -8122,6 +8158,45 @@ function initOaDirectory(repoRoot) {
8122
8158
  function hasOaDirectory(repoRoot) {
8123
8159
  return existsSync9(join14(repoRoot, OA_DIR, "index"));
8124
8160
  }
8161
+ function loadProjectSettings(repoRoot) {
8162
+ const settingsPath = join14(repoRoot, OA_DIR, "settings.json");
8163
+ try {
8164
+ if (existsSync9(settingsPath)) {
8165
+ return JSON.parse(readFileSync7(settingsPath, "utf-8"));
8166
+ }
8167
+ } catch {
8168
+ }
8169
+ return {};
8170
+ }
8171
+ function saveProjectSettings(repoRoot, settings) {
8172
+ const oaPath = join14(repoRoot, OA_DIR);
8173
+ mkdirSync4(oaPath, { recursive: true });
8174
+ const existing = loadProjectSettings(repoRoot);
8175
+ const merged = { ...existing, ...settings };
8176
+ writeFileSync4(join14(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
8177
+ }
8178
+ function loadGlobalSettings() {
8179
+ const settingsPath = join14(homedir4(), ".open-agents", "settings.json");
8180
+ try {
8181
+ if (existsSync9(settingsPath)) {
8182
+ return JSON.parse(readFileSync7(settingsPath, "utf-8"));
8183
+ }
8184
+ } catch {
8185
+ }
8186
+ return {};
8187
+ }
8188
+ function saveGlobalSettings(settings) {
8189
+ const dir = join14(homedir4(), ".open-agents");
8190
+ mkdirSync4(dir, { recursive: true });
8191
+ const existing = loadGlobalSettings();
8192
+ const merged = { ...existing, ...settings };
8193
+ writeFileSync4(join14(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
8194
+ }
8195
+ function resolveSettings(repoRoot) {
8196
+ const global = loadGlobalSettings();
8197
+ const project = loadProjectSettings(repoRoot);
8198
+ return { ...global, ...project };
8199
+ }
8125
8200
  function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
8126
8201
  const found = [];
8127
8202
  const seen = /* @__PURE__ */ new Set();
@@ -8364,7 +8439,7 @@ var init_oa_directory = __esm({
8364
8439
  "packages/cli/dist/tui/oa-directory.js"() {
8365
8440
  "use strict";
8366
8441
  OA_DIR = ".oa";
8367
- SUBDIRS = ["memory", "index", "context", "history"];
8442
+ SUBDIRS = ["memory", "index", "context", "history", "notes", "embedded", "provenance"];
8368
8443
  CONTEXT_FILES = [
8369
8444
  "AGENTS.md",
8370
8445
  "OA.md",
@@ -8401,7 +8476,7 @@ var init_oa_directory = __esm({
8401
8476
  import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync5 } from "node:fs";
8402
8477
  import { join as join15, basename as basename3 } from "node:path";
8403
8478
  import { execSync as execSync9 } from "node:child_process";
8404
- import { homedir as homedir4, platform, release } from "node:os";
8479
+ import { homedir as homedir5, platform, release } from "node:os";
8405
8480
  function loadProjectFiles(repoRoot) {
8406
8481
  const discovered = discoverContextFiles(repoRoot);
8407
8482
  if (discovered.length === 0)
@@ -8473,7 +8548,7 @@ function loadMemoryContext(repoRoot) {
8473
8548
  if (legacyEntries)
8474
8549
  sections.push(legacyEntries);
8475
8550
  }
8476
- const globalMemDir = join15(homedir4(), ".open-agents", "memory");
8551
+ const globalMemDir = join15(homedir5(), ".open-agents", "memory");
8477
8552
  const globalEntries = loadMemoryDir(globalMemDir, "global");
8478
8553
  if (globalEntries)
8479
8554
  sections.push(globalEntries);
@@ -8586,8 +8661,30 @@ var init_project_context = __esm({
8586
8661
  function fg(code, text) {
8587
8662
  return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
8588
8663
  }
8664
+ function displayWidth(str) {
8665
+ let w = 0;
8666
+ for (const ch of str) {
8667
+ const cp = ch.codePointAt(0);
8668
+ if (cp >= 4352 && cp <= 4447 || // Hangul Jamo
8669
+ cp >= 11904 && cp <= 12350 || // CJK Radicals, Kangxi, CJK Symbols
8670
+ cp >= 12352 && cp <= 13247 || // Hiragana, Katakana, CJK Compat
8671
+ cp >= 13312 && cp <= 19903 || // CJK Ext A
8672
+ cp >= 19968 && cp <= 42191 || // CJK Unified, Yi
8673
+ cp >= 44032 && cp <= 55215 || // Hangul Syllables
8674
+ cp >= 63744 && cp <= 64255 || // CJK Compat Ideographs
8675
+ cp >= 65072 && cp <= 65135 || // CJK Compat Forms
8676
+ cp >= 65281 && cp <= 65376 || // Fullwidth Forms
8677
+ cp >= 65504 && cp <= 65510 || // Fullwidth Signs
8678
+ cp >= 131072 && cp <= 195103) {
8679
+ w += 2;
8680
+ } else {
8681
+ w += 1;
8682
+ }
8683
+ }
8684
+ return w;
8685
+ }
8589
8686
  function buildPlainRibbon(phrases, repeatWidth) {
8590
- const separator = " \xB7 ";
8687
+ const separator = " : ";
8591
8688
  let plain = "";
8592
8689
  while (plain.length < repeatWidth * 3) {
8593
8690
  for (const p of phrases) {
@@ -8623,70 +8720,67 @@ var init_carousel = __esm({
8623
8720
  { text: "imagine freely", color: 219 },
8624
8721
  { text: "liberate creativity", color: 111 },
8625
8722
  // Spanish
8626
- { text: "libertad de informaci\xF3n", color: 214 },
8723
+ { text: "libertad de informacion", color: 214 },
8627
8724
  { text: "crear libremente", color: 208 },
8628
- { text: "c\xF3digo abierto", color: 220 },
8629
- { text: "s\xE9 creativo", color: 226 },
8630
- { text: "imaginaci\xF3n sin l\xEDmites", color: 215 },
8725
+ { text: "codigo abierto", color: 220 },
8726
+ { text: "se creativo", color: 226 },
8727
+ { text: "imaginacion sin limites", color: 215 },
8631
8728
  // French
8632
- { text: "cr\xE9er librement", color: 171 },
8633
- { text: "libert\xE9 d'expression", color: 177 },
8729
+ { text: "creer librement", color: 171 },
8730
+ { text: "liberte d'expression", color: 177 },
8634
8731
  { text: "source ouverte", color: 141 },
8635
8732
  { text: "imaginer sans contrainte", color: 183 },
8636
8733
  // German
8637
8734
  { text: "Freiheit der Muster", color: 77 },
8638
8735
  { text: "frei erschaffen", color: 78 },
8639
8736
  { text: "offene Quelle", color: 114 },
8640
- { text: "grenzenlose Kreativit\xE4t", color: 150 },
8737
+ { text: "grenzenlose Kreativitat", color: 150 },
8641
8738
  // Japanese
8642
- { text: "\u81EA\u7531\u306B\u5275\u9020\u3059\u308B", color: 204 },
8643
- { text: "\u958B\u6E90\u306E\u7CBE\u795E", color: 210 },
8644
- { text: "\u5236\u7D04\u306A\u304D\u60F3\u50CF\u529B", color: 203 },
8645
- { text: "\u77E5\u8B58\u3092\u5171\u6709\u3059\u308B", color: 209 },
8739
+ { text: "jiyuu ni souzou suru", color: 204 },
8740
+ { text: "kaigen no seishin", color: 210 },
8741
+ { text: "seiyaku naki souzouryoku", color: 203 },
8742
+ { text: "chishiki wo kyouyuu", color: 209 },
8646
8743
  // Korean
8647
- { text: "\uC790\uC720\uB85C\uC6B4 \uCC3D\uC791", color: 105 },
8648
- { text: "\uC624\uD508\uC18C\uC2A4 \uC815\uC2E0", color: 99 },
8649
- { text: "\uC601\uAC10\uC744 \uB098\uB204\uB2E4", color: 135 },
8650
- // Chinese
8651
- { text: "\u958B\u6E90\u81EA\u7531", color: 167 },
8652
- { text: "\u81EA\u7531\u5275\u4F5C", color: 173 },
8653
- { text: "\u77E5\u8B58\u5171\u4EAB", color: 161 },
8654
- { text: "\u7121\u9650\u60F3\u50CF", color: 131 },
8744
+ { text: "jayuroun changjak", color: 105 },
8745
+ { text: "opeunsoseu jeongsin", color: 99 },
8746
+ { text: "yeonggam eul nanuda", color: 135 },
8655
8747
  // Russian
8656
- { text: "\u0441\u0432\u043E\u0431\u043E\u0434\u0430 \u0442\u0432\u043E\u0440\u0447\u0435\u0441\u0442\u0432\u0430", color: 73 },
8657
- { text: "\u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0439 \u0438\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434", color: 67 },
8658
- { text: "\u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0439 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u043E", color: 109 },
8748
+ { text: "svoboda tvorchestva", color: 73 },
8749
+ { text: "otkrytyy iskhodnyy kod", color: 67 },
8750
+ { text: "sozdavay svobodno", color: 109 },
8659
8751
  // Portuguese
8660
8752
  { text: "liberdade de criar", color: 179 },
8661
- { text: "c\xF3digo aberto", color: 185 },
8662
- { text: "imagina\xE7\xE3o sem fronteiras", color: 186 },
8753
+ { text: "codigo aberto", color: 185 },
8754
+ { text: "imaginacao sem fronteiras", color: 186 },
8663
8755
  // Italian
8664
8756
  { text: "creare liberamente", color: 144 },
8665
- { text: "libert\xE0 di espressione", color: 180 },
8666
- // Hindi
8667
- { text: "\u0916\u0941\u0932\u0940 \u0938\u094B\u091A", color: 198 },
8668
- { text: "\u092E\u0941\u0915\u094D\u0924 \u0930\u091A\u0928\u093E", color: 199 },
8669
- // Arabic
8670
- { text: "\u062D\u0631\u064A\u0629 \u0627\u0644\u0625\u0628\u062F\u0627\u0639", color: 222 },
8671
- { text: "\u0645\u0635\u062F\u0631 \u0645\u0641\u062A\u0648\u062D", color: 228 },
8757
+ { text: "liberta di espressione", color: 180 },
8672
8758
  // Turkish
8673
- { text: "\xF6zg\xFCrce yarat", color: 156 },
8674
- { text: "a\xE7\u0131k kaynak", color: 192 },
8759
+ { text: "ozgurce yarat", color: 156 },
8760
+ { text: "acik kaynak", color: 192 },
8675
8761
  // Swedish
8676
8762
  { text: "skapa fritt", color: 116 },
8677
- { text: "\xF6ppen k\xE4llkod", color: 122 },
8763
+ { text: "oppen kallkod", color: 122 },
8678
8764
  // Dutch
8679
- { text: "vrij cre\xEBren", color: 152 },
8765
+ { text: "vrij creeren", color: 152 },
8680
8766
  // Polish
8681
- { text: "tw\xF3rz swobodnie", color: 188 },
8767
+ { text: "tworz swobodnie", color: 188 },
8682
8768
  // Greek
8683
- { text: "\u03B4\u03B7\u03BC\u03B9\u03BF\u03CD\u03C1\u03B3\u03B7\u03C3\u03B5 \u03B5\u03BB\u03B5\u03CD\u03B8\u03B5\u03C1\u03B1", color: 75 }
8769
+ { text: "dimiourgia elefthera", color: 75 },
8770
+ // Hindi
8771
+ { text: "khuli soch", color: 198 },
8772
+ { text: "mukt rachna", color: 199 },
8773
+ // Arabic
8774
+ { text: "hurriyat al-ibdaa", color: 222 },
8775
+ { text: "masdar maftuh", color: 228 }
8684
8776
  ];
8685
8777
  Carousel = class {
8686
8778
  rows;
8687
8779
  timer = null;
8688
8780
  width;
8689
8781
  lineCount = 3;
8782
+ /** Total rows reserved at top: 3 carousel + 1 blank separator */
8783
+ reservedRows = 4;
8690
8784
  started = false;
8691
8785
  resizeHandler = null;
8692
8786
  constructor() {
@@ -8696,11 +8790,8 @@ var init_carousel = __esm({
8696
8790
  const indices2 = Array.from({ length: PHRASES.length }, (_, i) => i).sort(() => Math.random() - 0.5);
8697
8791
  this.rows = [
8698
8792
  createRow(indices0, 2, -1),
8699
- // fast left
8700
8793
  createRow(indices1, 1, 1),
8701
- // medium right
8702
8794
  createRow(indices2, 1.5, -1)
8703
- // slow-medium left
8704
8795
  ];
8705
8796
  this.rebuildRibbons();
8706
8797
  }
@@ -8711,13 +8802,8 @@ var init_carousel = __esm({
8711
8802
  }
8712
8803
  /**
8713
8804
  * Start the animation.
8714
- *
8715
- * 1. Writes 3 empty lines to reserve space
8716
- * 2. Sets DECSTBM scroll region to row 4+ (everything below carousel)
8717
- * 3. Moves cursor into the scroll region
8718
- * 4. Starts animation timer that writes ONLY to rows 1-3
8719
- *
8720
- * The cursor never leaves the scroll region, so readline works perfectly.
8805
+ * Reserves rows 1-3 for carousel, row 4 as blank separator.
8806
+ * Sets scroll region to row 5+ for all content/readline.
8721
8807
  */
8722
8808
  start() {
8723
8809
  if (!isTTY3)
@@ -8725,17 +8811,17 @@ var init_carousel = __esm({
8725
8811
  this.started = true;
8726
8812
  const termRows = process.stdout.rows ?? 24;
8727
8813
  process.stdout.write("\x1B[1;1H");
8728
- for (let i = 0; i < this.lineCount; i++) {
8814
+ for (let i = 0; i < this.reservedRows; i++) {
8729
8815
  process.stdout.write("\x1B[2K\n");
8730
8816
  }
8731
- process.stdout.write(`\x1B[${this.lineCount + 1};${termRows}r`);
8732
- process.stdout.write(`\x1B[${this.lineCount + 1};1H`);
8817
+ process.stdout.write(`\x1B[${this.reservedRows + 1};${termRows}r`);
8818
+ process.stdout.write(`\x1B[${this.reservedRows + 1};1H`);
8733
8819
  this.resizeHandler = () => {
8734
8820
  this.width = process.stdout.columns ?? 80;
8735
8821
  const newRows = process.stdout.rows ?? 24;
8736
8822
  this.rebuildRibbons();
8737
8823
  if (this.started) {
8738
- process.stdout.write(`\x1B[${this.lineCount + 1};${newRows}r`);
8824
+ process.stdout.write(`\x1B[${this.reservedRows + 1};${newRows}r`);
8739
8825
  }
8740
8826
  };
8741
8827
  process.stdout.on("resize", this.resizeHandler);
@@ -8749,40 +8835,41 @@ var init_carousel = __esm({
8749
8835
  }
8750
8836
  this.renderFrame();
8751
8837
  }, 66);
8752
- return this.lineCount;
8838
+ return this.reservedRows;
8753
8839
  }
8754
8840
  /**
8755
- * Render one animation frame.
8756
- *
8757
- * Uses save/restore cursor (DECSC/DECRC) to briefly visit rows 1-3,
8758
- * then return. Because the scroll region is set to row 4+, the cursor
8759
- * restore puts it back exactly where readline expects it.
8841
+ * Render one animation frame into rows 1-3 only.
8842
+ * Row 4 is left blank as a separator.
8760
8843
  */
8761
8844
  renderFrame() {
8762
8845
  if (!isTTY3)
8763
8846
  return;
8764
8847
  let buf = "\x1B7";
8848
+ buf += "\x1B[?7l";
8765
8849
  for (let i = 0; i < this.rows.length; i++) {
8766
8850
  const line = this.extractWindow(this.rows[i]);
8767
8851
  buf += `\x1B[${i + 1};1H\x1B[2K${line}`;
8768
8852
  }
8853
+ buf += `\x1B[4;1H\x1B[2K`;
8854
+ buf += "\x1B[?7h";
8769
8855
  buf += "\x1B8";
8770
8856
  process.stdout.write(buf);
8771
8857
  }
8772
8858
  /**
8773
8859
  * Extract a terminal-width colored window from a scrolling ribbon.
8860
+ * Uses column-aware width to prevent CJK/fullwidth character overflow.
8774
8861
  */
8775
8862
  extractWindow(row) {
8776
- const w = this.width;
8863
+ const maxCols = this.width;
8777
8864
  const plain = row.renderedPlain;
8778
8865
  if (!plain)
8779
8866
  return "";
8780
8867
  let start = Math.floor(Math.abs(row.offset)) % plain.length;
8781
8868
  if (start < 0)
8782
8869
  start += plain.length;
8783
- const separator = " \xB7 ";
8870
+ const separator = " : ";
8784
8871
  let coloredLine = "";
8785
- let charCount = 0;
8872
+ let colCount = 0;
8786
8873
  let charInPhrase = 0;
8787
8874
  let skipChars = start;
8788
8875
  const phrases = row.phrases;
@@ -8797,20 +8884,25 @@ var init_carousel = __esm({
8797
8884
  skipChars -= fullText.length;
8798
8885
  fullIdx++;
8799
8886
  }
8800
- while (charCount < w) {
8887
+ while (colCount < maxCols) {
8801
8888
  const p = phrases[fullIdx % phrases.length];
8802
8889
  const fullText = p.text + separator;
8803
8890
  const remaining = fullText.slice(charInPhrase);
8804
8891
  for (const ch of remaining) {
8805
- if (charCount >= w)
8892
+ const chWidth = displayWidth(ch);
8893
+ if (colCount + chWidth > maxCols) {
8894
+ if (colCount < maxCols)
8895
+ coloredLine += " ";
8896
+ colCount = maxCols;
8806
8897
  break;
8898
+ }
8807
8899
  const inPhrase = charInPhrase < p.text.length;
8808
8900
  if (inPhrase) {
8809
8901
  coloredLine += fg(p.color, ch);
8810
8902
  } else {
8811
8903
  coloredLine += `\x1B[2m${ch}\x1B[0m`;
8812
8904
  }
8813
- charCount++;
8905
+ colCount += chWidth;
8814
8906
  charInPhrase++;
8815
8907
  }
8816
8908
  fullIdx++;
@@ -8819,7 +8911,7 @@ var init_carousel = __esm({
8819
8911
  return coloredLine;
8820
8912
  }
8821
8913
  /**
8822
- * Stop the animation, clear the carousel lines, and reset scroll region.
8914
+ * Stop the animation, clear rows 1-4, reset scroll region.
8823
8915
  */
8824
8916
  stop() {
8825
8917
  if (this.timer) {
@@ -8833,7 +8925,7 @@ var init_carousel = __esm({
8833
8925
  if (!isTTY3 || !this.started)
8834
8926
  return;
8835
8927
  let buf = "\x1B7";
8836
- for (let i = 0; i < this.lineCount; i++) {
8928
+ for (let i = 0; i < this.reservedRows; i++) {
8837
8929
  buf += `\x1B[${i + 1};1H\x1B[2K`;
8838
8930
  }
8839
8931
  buf += "\x1B[r";
@@ -8841,7 +8933,6 @@ var init_carousel = __esm({
8841
8933
  process.stdout.write(buf);
8842
8934
  this.started = false;
8843
8935
  }
8844
- /** Check if carousel is running */
8845
8936
  get isRunning() {
8846
8937
  return this.timer !== null;
8847
8938
  }
@@ -8852,7 +8943,7 @@ var init_carousel = __esm({
8852
8943
  // packages/cli/dist/tui/voice.js
8853
8944
  import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, readFileSync as readFileSync9, unlinkSync } from "node:fs";
8854
8945
  import { join as join16 } from "node:path";
8855
- import { homedir as homedir5, tmpdir as tmpdir2, platform as platform2 } from "node:os";
8946
+ import { homedir as homedir6, tmpdir as tmpdir2, platform as platform2 } from "node:os";
8856
8947
  import { execSync as execSync10, spawn as nodeSpawn } from "node:child_process";
8857
8948
  import { createRequire } from "node:module";
8858
8949
  function modelDir(id) {
@@ -8971,7 +9062,7 @@ var init_voice = __esm({
8971
9062
  configUrl: "https://raw.githubusercontent.com/robit-man/combine_overwatch_onnx/main/overwatch.onnx.json"
8972
9063
  }
8973
9064
  };
8974
- VOICE_DIR = join16(homedir5(), ".open-agents", "voice");
9065
+ VOICE_DIR = join16(homedir6(), ".open-agents", "voice");
8975
9066
  MODELS_DIR = join16(VOICE_DIR, "models");
8976
9067
  VoiceEngine = class {
8977
9068
  enabled = false;
@@ -9620,6 +9711,19 @@ function startTask(task, config, repoRoot, voice) {
9620
9711
  }
9621
9712
  async function startInteractive(config, repoPath) {
9622
9713
  const repoRoot = resolve11(repoPath ?? cwd());
9714
+ initOaDirectory(repoRoot);
9715
+ const savedSettings = resolveSettings(repoRoot);
9716
+ if (savedSettings.model)
9717
+ config = { ...config, model: savedSettings.model };
9718
+ if (savedSettings.backendUrl)
9719
+ config = { ...config, backendUrl: savedSettings.backendUrl };
9720
+ if (savedSettings.backendType && ["ollama", "vllm", "fake"].includes(savedSettings.backendType)) {
9721
+ config = { ...config, backendType: savedSettings.backendType };
9722
+ }
9723
+ if (savedSettings.apiKey)
9724
+ config = { ...config, apiKey: savedSettings.apiKey };
9725
+ if (savedSettings.verbose !== void 0)
9726
+ config = { ...config, verbose: savedSettings.verbose };
9623
9727
  const needsSetup = isFirstRun() || !await isModelAvailable(config);
9624
9728
  if (needsSetup && config.backendType === "ollama") {
9625
9729
  const setupModel = await runSetupWizard(config);
@@ -9652,6 +9756,14 @@ async function startInteractive(config, repoPath) {
9652
9756
  carouselLines
9653
9757
  });
9654
9758
  const voiceEngine = new VoiceEngine();
9759
+ if (savedSettings.voice) {
9760
+ voiceEngine.toggle().catch(() => {
9761
+ });
9762
+ if (savedSettings.voiceModel) {
9763
+ voiceEngine.setModel(savedSettings.voiceModel).catch(() => {
9764
+ });
9765
+ }
9766
+ }
9655
9767
  let currentConfig = { ...config };
9656
9768
  let activeTask = null;
9657
9769
  let messageQueue = [];
@@ -9668,8 +9780,9 @@ async function startInteractive(config, repoPath) {
9668
9780
  process.stdout.on("resize", () => {
9669
9781
  if (!carouselRetired) {
9670
9782
  const termRows = process.stdout.rows ?? 24;
9671
- process.stdout.write(`\x1B[${carouselLines + 1};${termRows}r`);
9672
- process.stdout.write(`\x1B[${carouselLines + 1};1H\x1B[J`);
9783
+ const scrollStart = carousel.reservedRows + 1;
9784
+ process.stdout.write(`\x1B[${scrollStart};${termRows}r`);
9785
+ process.stdout.write(`\x1B[${scrollStart};1H\x1B[J`);
9673
9786
  renderRichHeader({
9674
9787
  model: currentConfig.model,
9675
9788
  version,
@@ -9716,6 +9829,13 @@ async function startInteractive(config, repoPath) {
9716
9829
  },
9717
9830
  async voiceSetModel(id) {
9718
9831
  return voiceEngine.setModel(id);
9832
+ },
9833
+ saveSettings(settings) {
9834
+ try {
9835
+ saveProjectSettings(repoRoot, settings);
9836
+ saveGlobalSettings(settings);
9837
+ } catch {
9838
+ }
9719
9839
  }
9720
9840
  };
9721
9841
  showPrompt();
@@ -10323,7 +10443,7 @@ __export(config_exports, {
10323
10443
  configCommand: () => configCommand
10324
10444
  });
10325
10445
  import { join as join19 } from "node:path";
10326
- import { homedir as homedir6 } from "node:os";
10446
+ import { homedir as homedir7 } from "node:os";
10327
10447
  async function configCommand(opts, config) {
10328
10448
  if (opts.subCommand === "set") {
10329
10449
  return handleSet(opts, config);
@@ -10345,7 +10465,7 @@ function handleShow(opts, config) {
10345
10465
  printKeyValue("verbose", String(config.verbose), 2);
10346
10466
  printKeyValue("dbPath", config.dbPath, 2);
10347
10467
  printSection("Config File");
10348
- printInfo(`~/.open-agents/config.json (${join19(homedir6(), ".open-agents", "config.json")})`);
10468
+ printInfo(`~/.open-agents/config.json (${join19(homedir7(), ".open-agents", "config.json")})`);
10349
10469
  printSection("Environment Variables");
10350
10470
  printInfo("OPEN_AGENTS_BACKEND_URL \u2014 override backendUrl");
10351
10471
  printInfo("OPEN_AGENTS_MODEL \u2014 override model");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.10.8",
3
+ "version": "0.11.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",