open-agents-ai 0.187.578 → 0.187.580

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
@@ -65,7 +65,7 @@ function parseBackendType(value2) {
65
65
  return VALID_BACKEND_TYPES.has(value2) ? value2 : void 0;
66
66
  }
67
67
  function loadConfigFile() {
68
- const configPath2 = join(homedir(), ".open-agents", "config.json");
68
+ const configPath2 = join(openAgentsConfigDir(), "config.json");
69
69
  if (!existsSync(configPath2)) return {};
70
70
  try {
71
71
  const raw = readFileSync(configPath2, "utf8");
@@ -110,13 +110,16 @@ function mergeConfig(base3, overrides) {
110
110
  return { ...base3, ...overrides };
111
111
  }
112
112
  function setConfigValue(key, value2) {
113
- const dir = join(homedir(), ".open-agents");
113
+ const dir = openAgentsConfigDir();
114
114
  mkdirSync(dir, { recursive: true });
115
115
  const configPath2 = join(dir, "config.json");
116
116
  const existing = loadConfigFile();
117
117
  existing[key] = coerceConfigValue(key, value2);
118
118
  writeFileSync(configPath2, JSON.stringify(existing, null, 2) + "\n", { encoding: "utf8", mode: 384 });
119
119
  }
120
+ function openAgentsConfigDir() {
121
+ return join(process.env["OPEN_AGENTS_CONFIG_HOME"] ?? homedir(), ".open-agents");
122
+ }
120
123
  function coerceConfigValue(key, value2) {
121
124
  const intKeys = /* @__PURE__ */ new Set(["maxRetries", "timeoutMs"]);
122
125
  const boolKeys = /* @__PURE__ */ new Set(["dryRun", "verbose"]);
@@ -569510,6 +569513,7 @@ var init_sponsor_wizard = __esm({
569510
569513
  var voice_exports = {};
569511
569514
  __export(voice_exports, {
569512
569515
  VoiceEngine: () => VoiceEngine,
569516
+ applySupertonicExpressionTags: () => applySupertonicExpressionTags,
569513
569517
  computeProsodyHint: () => computeProsodyHint,
569514
569518
  describeTaskComplete: () => describeTaskComplete,
569515
569519
  describeToolCall: () => describeToolCall,
@@ -569597,7 +569601,7 @@ function supertonicProfilesFile() {
569597
569601
  return join106(voiceDir(), "supertonic3-profiles.json");
569598
569602
  }
569599
569603
  function isSupertonicExpression(value2) {
569600
- return value2 === "none" || value2 === "laugh" || value2 === "breath" || value2 === "sigh";
569604
+ return value2 === "auto" || value2 === "none" || value2 === "laugh" || value2 === "breath" || value2 === "sigh";
569601
569605
  }
569602
569606
  function normalizeSupertonicSettings(value2) {
569603
569607
  const v = value2 && typeof value2 === "object" ? value2 : {};
@@ -569622,9 +569626,122 @@ function clampFloat(value2, fallback, min, max) {
569622
569626
  function clampInt(value2, fallback, min, max) {
569623
569627
  return Math.round(clampFloat(value2, fallback, min, max));
569624
569628
  }
569625
- function applySupertonicExpression(text, expression) {
569626
- if (!text.trim() || expression === "none") return text;
569627
- return `<${expression}> ${text}`;
569629
+ function applySupertonicExpressionTags(text, expression, emotion) {
569630
+ const cleaned = text.trim();
569631
+ if (!cleaned || expression === "none") return cleaned;
569632
+ if (SUPERTONIC_EXPRESSION_TAG_RE.test(cleaned)) {
569633
+ return normalizeSupertonicTagSpacing(cleaned);
569634
+ }
569635
+ if (expression !== "auto") {
569636
+ return injectSupertonicTag(cleaned, expression);
569637
+ }
569638
+ const plan = inferSupertonicExpressionPlan(cleaned, emotion);
569639
+ if (!plan.primary) return cleaned;
569640
+ let expressive = injectSupertonicTag(cleaned, plan.primary);
569641
+ if (plan.boundaryBreath && !expressive.includes("<breath>")) {
569642
+ expressive = injectBreathAtSentenceBoundary(expressive);
569643
+ }
569644
+ if (plan.secondary && !expressive.includes(`<${plan.secondary}>`)) {
569645
+ expressive = injectSupertonicTag(expressive, plan.secondary);
569646
+ }
569647
+ return normalizeSupertonicTagSpacing(expressive);
569648
+ }
569649
+ function normalizeSupertonicTagSpacing(text) {
569650
+ return text.replace(/\s*(<(?:laugh|breath|sigh)>)\s*/gi, " $1 ").replace(/\s{2,}/g, " ").trim();
569651
+ }
569652
+ function inferSupertonicExpressionPlan(text, emotion) {
569653
+ const lower = text.toLowerCase();
569654
+ const hasFailure = /\b(error|failed|failure|failing|issue|exception|timeout|blocked|cannot|can't|could not|incomplete|crash|missing|denied|rejected|unreadable|stale|invalid)\b/.test(
569655
+ lower
569656
+ );
569657
+ const persistentFailure = hasFailure && /\b(again|repeated|persistent|consecutive|second|third|keeps|still|retry|attempt)\b/.test(
569658
+ lower
569659
+ );
569660
+ const hasSuccess = /\b(success|successful|successfully|complete|completed|done|clean|passed|green|fixed|resolved|ready|excellent|great|nice|all clean|zero errors)\b/.test(
569661
+ lower
569662
+ );
569663
+ const playful = /\b(playful|fun|delight|spark|nice|great|excellent|tiny victory|clean run)\b/.test(
569664
+ lower
569665
+ );
569666
+ const valence = emotion?.valence ?? 0;
569667
+ const arousal = emotion?.arousal ?? 0.3;
569668
+ const stressed = valence < -0.2 && arousal > 0.5;
569669
+ const subdued = valence < -0.2 && arousal <= 0.5;
569670
+ const excited = valence > 0.3 && arousal > 0.5;
569671
+ const multiSentence = /[.!?]\s+\S/.test(text);
569672
+ if (persistentFailure) {
569673
+ return { primary: "sigh", secondary: stressed ? "breath" : void 0 };
569674
+ }
569675
+ if (hasFailure) {
569676
+ return {
569677
+ primary: "sigh",
569678
+ secondary: stressed ? "breath" : void 0,
569679
+ boundaryBreath: multiSentence
569680
+ };
569681
+ }
569682
+ if (excited && (hasSuccess || playful)) {
569683
+ return { primary: "laugh" };
569684
+ }
569685
+ if (hasSuccess && playful) {
569686
+ return { primary: "laugh" };
569687
+ }
569688
+ if (stressed) {
569689
+ return { primary: "breath", boundaryBreath: multiSentence };
569690
+ }
569691
+ if (subdued && text.length > 80) {
569692
+ return { primary: "sigh" };
569693
+ }
569694
+ if (arousal > 0.75 && text.length > 90) {
569695
+ return { primary: "breath", boundaryBreath: multiSentence };
569696
+ }
569697
+ return { primary: null };
569698
+ }
569699
+ function injectBreathAtSentenceBoundary(text) {
569700
+ return text.replace(/([.!?])\s+(?=\S)/, `$1 <breath> `);
569701
+ }
569702
+ function injectSupertonicTag(text, tag) {
569703
+ if (tag === "laugh") {
569704
+ const opener = text.match(/^(nice|great|excellent|beautiful|perfect|lovely)\b[,!.]?\s*/i);
569705
+ if (opener) {
569706
+ return `${opener[0].trim()} <laugh> ${text.slice(opener[0].length).trim()}`;
569707
+ }
569708
+ const successIdx = text.search(
569709
+ /\b(successfully|passed|clean|all clean|zero errors|ready|resolved|green|complete|completed|done)\b/i
569710
+ );
569711
+ if (successIdx >= 0) {
569712
+ return insertTagAfterMatch(
569713
+ text,
569714
+ /\b(successfully|passed|clean|all clean|zero errors|ready|resolved|green|complete|completed|done)\b/i,
569715
+ tag
569716
+ );
569717
+ }
569718
+ return insertTagAfterOpeningClause(text, tag);
569719
+ }
569720
+ if (tag === "sigh") {
569721
+ const failure = /\b(failed again|keeps failing|failed|failure|failing|hit an issue|returned an error|could not finish|incomplete|timed out|timeout|blocked)\b/i;
569722
+ if (failure.test(text)) return insertTagBeforeMatch(text, failure, tag);
569723
+ return `<${tag}> ${text}`;
569724
+ }
569725
+ const pivot = /,\s*(pushing through|moving forward|being careful|addressing|retrying|checking|continuing)\b/i;
569726
+ if (pivot.test(text)) {
569727
+ return text.replace(pivot, (_match, phrase) => `, <${tag}> ${phrase}`);
569728
+ }
569729
+ return insertTagAfterOpeningClause(text, tag);
569730
+ }
569731
+ function insertTagAfterMatch(text, pattern, tag) {
569732
+ return text.replace(pattern, (match) => `${match} <${tag}>`);
569733
+ }
569734
+ function insertTagBeforeMatch(text, pattern, tag) {
569735
+ return text.replace(pattern, (match) => `<${tag}> ${match}`);
569736
+ }
569737
+ function insertTagAfterOpeningClause(text, tag) {
569738
+ const clause = text.match(/^(.{24,96}?[,:;.])\s+(.+)$/);
569739
+ if (clause) return `${clause[1]} <${tag}> ${clause[2]}`;
569740
+ const words = text.split(/\s+/);
569741
+ if (words.length > 6) {
569742
+ return `${words.slice(0, 5).join(" ")} <${tag}> ${words.slice(5).join(" ")}`;
569743
+ }
569744
+ return `<${tag}> ${text}`;
569628
569745
  }
569629
569746
  function writeDetectTorchScript(targetPath) {
569630
569747
  if (existsSync89(targetPath)) return;
@@ -570292,7 +570409,7 @@ function formatBytes2(bytes) {
570292
570409
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
570293
570410
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
570294
570411
  }
570295
- var VOICE_MODELS, SUPERTONIC_LANGS, SUPERTONIC_VOICES, DEFAULT_SUPERTONIC_SETTINGS, SUPERTONIC_INFER_PY, VoiceEngine, RING_BUFFER_SIZE, narration;
570412
+ var VOICE_MODELS, SUPERTONIC_LANGS, SUPERTONIC_VOICES, DEFAULT_SUPERTONIC_SETTINGS, SUPERTONIC_EXPRESSION_TAG_RE, SUPERTONIC_INFER_PY, VoiceEngine, RING_BUFFER_SIZE, narration;
570296
570413
  var init_voice = __esm({
570297
570414
  "packages/cli/src/tui/voice.ts"() {
570298
570415
  "use strict";
@@ -570433,8 +570550,9 @@ var init_voice = __esm({
570433
570550
  lang: "en",
570434
570551
  speed: 1.05,
570435
570552
  totalStep: 8,
570436
- expression: "none"
570553
+ expression: "auto"
570437
570554
  };
570555
+ SUPERTONIC_EXPRESSION_TAG_RE = /<(?:laugh|breath|sigh)>/i;
570438
570556
  SUPERTONIC_INFER_PY = String.raw`
570439
570557
  import json, sys, traceback
570440
570558
 
@@ -570851,7 +570969,7 @@ except Exception as exc:
570851
570969
  speak(text, emotion) {
570852
570970
  const pitchBias = emotion ? emotionToPitchBias(emotion, this.starkMode, this.autistMode) : 0;
570853
570971
  const speedFactor = emotion ? emotionToSpeedFactor(emotion, this.starkMode, this.autistMode) : 1;
570854
- this.enqueueSpeech(text, 1, 1 + pitchBias, speedFactor, 0.6);
570972
+ this.enqueueSpeech(text, 1, 1 + pitchBias, speedFactor, 0.6, emotion);
570855
570973
  }
570856
570974
  /**
570857
570975
  * Speak text at reduced volume with narrow stereo placement to indicate
@@ -570863,7 +570981,7 @@ except Exception as exc:
570863
570981
  speakSubordinate(text, emotion) {
570864
570982
  const pitchBias = emotion ? emotionToPitchBias(emotion, this.starkMode, this.autistMode) : 0;
570865
570983
  const speedFactor = emotion ? emotionToSpeedFactor(emotion, this.starkMode, this.autistMode) : 1;
570866
- this.enqueueSpeech(text, 0.55, 1 + pitchBias, speedFactor, 0.15);
570984
+ this.enqueueSpeech(text, 0.55, 1 + pitchBias, speedFactor, 0.15, emotion);
570867
570985
  }
570868
570986
  /** Wait until the speak queue is fully drained (all audio played). */
570869
570987
  async waitUntilIdle() {
@@ -570874,11 +570992,20 @@ except Exception as exc:
570874
570992
  await this.sleep(100);
570875
570993
  }
570876
570994
  }
570877
- enqueueSpeech(text, volume, pitchFactor, speedFactor = 1, stereoDelayMs = 0.6) {
570995
+ enqueueSpeech(text, volume, pitchFactor, speedFactor = 1, stereoDelayMs = 0.6, emotion) {
570878
570996
  if (!this.enabled || !this.ready) return;
570879
570997
  text = sanitizeForTTS(text);
570880
570998
  if (!text.trim()) return;
570881
570999
  const chunks = this.chunkText(text);
571000
+ const supertonicTagsPrepared = this.supertonicActive;
571001
+ if (supertonicTagsPrepared && chunks.length > 0) {
571002
+ const settings = this.getSupertonicSettings();
571003
+ chunks[0] = applySupertonicExpressionTags(
571004
+ chunks[0],
571005
+ settings.expression,
571006
+ emotion
571007
+ );
571008
+ }
570882
571009
  if (this.speakQueue.length >= 30) {
570883
571010
  return;
570884
571011
  }
@@ -570888,7 +571015,9 @@ except Exception as exc:
570888
571015
  volume,
570889
571016
  pitchFactor,
570890
571017
  speedFactor,
570891
- stereoDelayMs
571018
+ stereoDelayMs,
571019
+ emotion,
571020
+ supertonicTagsPrepared
570892
571021
  });
570893
571022
  }
570894
571023
  if (!this.speaking) {
@@ -571147,7 +571276,9 @@ except Exception as exc:
571147
571276
  item.volume,
571148
571277
  item.pitchFactor,
571149
571278
  item.speedFactor,
571150
- item.stereoDelayMs
571279
+ item.stereoDelayMs,
571280
+ item.emotion,
571281
+ item.supertonicTagsPrepared
571151
571282
  );
571152
571283
  }
571153
571284
  } catch {
@@ -571175,7 +571306,7 @@ except Exception as exc:
571175
571306
  // -------------------------------------------------------------------------
571176
571307
  // Synthesis pipeline
571177
571308
  // -------------------------------------------------------------------------
571178
- async synthesizeAndPlay(text, volume = 1, pitchFactor = 1, speedFactor = 1, stereoDelayMs = 0.6) {
571309
+ async synthesizeAndPlay(text, volume = 1, pitchFactor = 1, speedFactor = 1, stereoDelayMs = 0.6, emotion, supertonicTagsPrepared = false) {
571179
571310
  if (this.luxttsActive) {
571180
571311
  await this.synthesizeWithLuxtts(
571181
571312
  text,
@@ -571192,7 +571323,9 @@ except Exception as exc:
571192
571323
  volume,
571193
571324
  pitchFactor,
571194
571325
  speedFactor,
571195
- stereoDelayMs
571326
+ stereoDelayMs,
571327
+ emotion,
571328
+ !supertonicTagsPrepared
571196
571329
  );
571197
571330
  return;
571198
571331
  }
@@ -571704,13 +571837,11 @@ except Exception as exc:
571704
571837
  child.stdin?.end(JSON.stringify(req2));
571705
571838
  });
571706
571839
  }
571707
- async synthesizeSupertonicWav(text, speedFactor = 1) {
571840
+ async synthesizeSupertonicWav(text, speedFactor = 1, emotion, injectExpressionTags = true) {
571708
571841
  await this.ensureSupertonic();
571709
571842
  const settings = this.getSupertonicSettings();
571710
- const cleaned = applySupertonicExpression(
571711
- text.replace(/\*/g, "").trim(),
571712
- settings.expression
571713
- );
571843
+ const baseText = sanitizeForTTS(text);
571844
+ const cleaned = injectExpressionTags ? applySupertonicExpressionTags(baseText, settings.expression, emotion) : baseText;
571714
571845
  if (!cleaned) return null;
571715
571846
  const wavPath = join106(
571716
571847
  tmpdir20(),
@@ -571730,8 +571861,13 @@ except Exception as exc:
571730
571861
  return null;
571731
571862
  }
571732
571863
  }
571733
- async synthesizeWithSupertonic(text, volume = 1, pitchFactor = 1, speedFactor = 1, stereoDelayMs = 0.6) {
571734
- const wavPath = await this.synthesizeSupertonicWav(text, speedFactor);
571864
+ async synthesizeWithSupertonic(text, volume = 1, pitchFactor = 1, speedFactor = 1, stereoDelayMs = 0.6, emotion, injectExpressionTags = true) {
571865
+ const wavPath = await this.synthesizeSupertonicWav(
571866
+ text,
571867
+ speedFactor,
571868
+ emotion,
571869
+ injectExpressionTags
571870
+ );
571735
571871
  if (!wavPath) return;
571736
571872
  await this.postProcessAndPlayLuxtts(
571737
571873
  wavPath,
@@ -579964,14 +580100,14 @@ async function handleSupertonicMenu(ctx3, save2) {
579964
580100
  ];
579965
580101
  const speeds = ["0.9", "1.0", "1.05", "1.2", "1.35", "1.5"];
579966
580102
  const steps = ["6", "8", "10", "12"];
579967
- const expressions = ["none", "laugh", "breath", "sigh"];
580103
+ const expressions = ["auto", "none", "laugh", "breath", "sigh"];
579968
580104
  while (true) {
579969
580105
  const st = ctx3.voiceGetSupertonicSettings?.() ?? {
579970
580106
  voiceName: "M4",
579971
580107
  lang: "en",
579972
580108
  speed: 1.05,
579973
580109
  totalStep: 8,
579974
- expression: "none"
580110
+ expression: "auto"
579975
580111
  };
579976
580112
  const profiles = ctx3.voiceListSupertonicProfiles?.() ?? [];
579977
580113
  const items = [
@@ -594439,6 +594575,18 @@ async function runCommand(input, opts) {
594439
594575
  const argsStr = rest.join(" ");
594440
594576
  const release = await acquireLock2();
594441
594577
  try {
594578
+ const quick2 = buildNonInteractiveSummary(cmdName, argsStr, opts?.config);
594579
+ if (quick2) {
594580
+ return {
594581
+ ok: true,
594582
+ command: cmdName,
594583
+ args: argsStr,
594584
+ kind: "handled",
594585
+ output: quick2,
594586
+ ansi: quick2,
594587
+ durationMs: Date.now() - start2
594588
+ };
594589
+ }
594442
594590
  const buf = [];
594443
594591
  setContentWriteHook({
594444
594592
  begin: () => {
@@ -594489,6 +594637,43 @@ async function runCommand(input, opts) {
594489
594637
  release();
594490
594638
  }
594491
594639
  }
594640
+ function buildNonInteractiveSummary(cmdName, _args, config) {
594641
+ const cfg = config ?? loadConfig();
594642
+ if (cmdName === "setup" || cmdName === "wizard") {
594643
+ return [
594644
+ "open-agents setup",
594645
+ "",
594646
+ "The setup wizard is an interactive terminal flow. In the GUI command bridge it is summarized instead of opening prompts, installing software, starting Ollama, or pulling models.",
594647
+ "",
594648
+ `Current backend: ${cfg.backendType ?? "ollama"}`,
594649
+ `Current endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
594650
+ `Current model: ${cfg.model ?? "qwen3.5:latest"}`,
594651
+ "",
594652
+ "Available non-interactive setup actions:",
594653
+ " /endpoint <url> Set or inspect the inference endpoint.",
594654
+ " /model <name> Set the active model directly.",
594655
+ " /models Show model-selection guidance.",
594656
+ " /config Inspect persisted configuration.",
594657
+ " /doctor Run diagnostics from the terminal if deeper repair is needed.",
594658
+ "",
594659
+ "Open a terminal and run `oa`, then use /setup for the full guided wizard."
594660
+ ].join("\n");
594661
+ }
594662
+ if (cmdName === "models") {
594663
+ return [
594664
+ "open-agents models",
594665
+ "",
594666
+ "The model picker is interactive in the TUI. The GUI bridge does not probe remote model endpoints here, so it cannot hang on a stale backend.",
594667
+ "",
594668
+ `Active model: ${cfg.model ?? "qwen3.5:latest"}`,
594669
+ `Endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
594670
+ `Backend: ${cfg.backendType ?? "ollama"}`,
594671
+ "",
594672
+ "Use /model <name> to set a model directly, /endpoint to switch providers, or open the TUI for the searchable model picker."
594673
+ ].join("\n");
594674
+ }
594675
+ return null;
594676
+ }
594492
594677
  function acquireLock2() {
594493
594678
  let release;
594494
594679
  const next = new Promise((res) => {
@@ -607320,6 +607505,7 @@ async function refreshEndpointRegistry() {
607320
607505
  type: config.backendType || "ollama",
607321
607506
  authKey: config.apiKey
607322
607507
  });
607508
+ if (process.env["OA_SKIP_SPONSOR_DISCOVERY"] === "1") return;
607323
607509
  try {
607324
607510
  const resp = await fetch("https://openagents.nexus/api/v1/sponsors", {
607325
607511
  signal: AbortSignal.timeout(5e3)
@@ -607440,6 +607626,16 @@ function getBackendTimeoutMs(perRequestSeconds) {
607440
607626
  }
607441
607627
  return BACKEND_TIMEOUT_DEFAULT_MS;
607442
607628
  }
607629
+ function getModelListTimeoutMs() {
607630
+ const envS = process.env["OA_MODEL_LIST_TIMEOUT_S"];
607631
+ if (envS) {
607632
+ const n2 = parseFloat(envS);
607633
+ if (Number.isFinite(n2) && n2 > 0) {
607634
+ return Math.min(Math.floor(n2 * 1e3), 3e4);
607635
+ }
607636
+ }
607637
+ return MODEL_LIST_TIMEOUT_DEFAULT_MS;
607638
+ }
607443
607639
  function recordMetric(method, path11, status) {
607444
607640
  const key = `${method}|${path11}|${status}`;
607445
607641
  const existing = metrics.requests.get(key);
@@ -607879,10 +608075,12 @@ function ollamaRequest(ollamaUrl, path11, method, body, timeoutMs) {
607879
608075
  }
607880
608076
  };
607881
608077
  const transport = isHttps ? https3 : http5;
608078
+ const _to = getBackendTimeoutMs(timeoutMs ? timeoutMs / 1e3 : void 0);
607882
608079
  const proxyReq = transport.request(options2, (proxyRes) => {
607883
608080
  const chunks = [];
607884
608081
  proxyRes.on("data", (chunk) => chunks.push(chunk));
607885
608082
  proxyRes.on("end", () => {
608083
+ clearTimeout(hardTimeout);
607886
608084
  resolve43({
607887
608085
  status: proxyRes.statusCode ?? 500,
607888
608086
  headers: proxyRes.headers,
@@ -607890,11 +608088,17 @@ function ollamaRequest(ollamaUrl, path11, method, body, timeoutMs) {
607890
608088
  });
607891
608089
  });
607892
608090
  });
607893
- const _to = getBackendTimeoutMs(timeoutMs ? timeoutMs / 1e3 : void 0);
608091
+ const hardTimeout = setTimeout(() => {
608092
+ proxyReq.destroy(new Error(`Backend request timeout (${Math.round(_to / 1e3)}s)`));
608093
+ }, _to);
608094
+ hardTimeout.unref();
607894
608095
  proxyReq.setTimeout(_to, () => {
607895
608096
  proxyReq.destroy(new Error(`Backend request timeout (${Math.round(_to / 1e3)}s)`));
607896
608097
  });
607897
- proxyReq.on("error", reject);
608098
+ proxyReq.on("error", (err) => {
608099
+ clearTimeout(hardTimeout);
608100
+ reject(err);
608101
+ });
607898
608102
  if (body) proxyReq.write(body);
607899
608103
  proxyReq.end();
607900
608104
  });
@@ -608670,7 +608874,7 @@ async function fetchAggregatedOllamaTags() {
608670
608874
  try {
608671
608875
  const isOllama = ep.type === "ollama";
608672
608876
  const path11 = isOllama ? "/api/tags" : "/v1/models";
608673
- const result = await ollamaRequest(ep.url, path11, "GET");
608877
+ const result = await ollamaRequest(ep.url, path11, "GET", void 0, getModelListTimeoutMs());
608674
608878
  if (result.status !== 200) return;
608675
608879
  const body = JSON.parse(result.body);
608676
608880
  if (isOllama) {
@@ -608727,6 +608931,10 @@ async function fetchAggregatedOllamaTags() {
608727
608931
  return out.sort((a2, b) => a2.name.localeCompare(b.name));
608728
608932
  }
608729
608933
  async function handleApiTags(res) {
608934
+ if (process.env["OA_API_TEST_MODE"] === "1") {
608935
+ jsonResponse(res, 200, { models: [] });
608936
+ return;
608937
+ }
608730
608938
  try {
608731
608939
  const models = await fetchAggregatedOllamaTags();
608732
608940
  jsonResponse(res, 200, { models });
@@ -611272,8 +611480,16 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
611272
611480
  jsonResponse(res, 400, { error: "missing_text" });
611273
611481
  return;
611274
611482
  }
611483
+ const voiceStatus = getRuntimeStatus();
611484
+ if (!voiceStatus.voiceReady) {
611485
+ jsonResponse(res, 501, {
611486
+ error: "tts_not_ready",
611487
+ message: "Voice synthesis is not ready in the daemon. Warm the voice runtime with POST /v1/voice/start before requesting audio.",
611488
+ state: voiceStatus
611489
+ });
611490
+ return;
611491
+ }
611275
611492
  try {
611276
- await ensureRuntime();
611277
611493
  const ve = getVoiceEngine();
611278
611494
  let prevModel = null;
611279
611495
  if (requestedModel && ve.modelId !== requestedModel) {
@@ -613510,6 +613726,7 @@ function startApiServer(options2 = {}) {
613510
613726
  if (options2.host) host = options2.host;
613511
613727
  if (options2.port) port = options2.port;
613512
613728
  const verbose = options2.verbose ?? false;
613729
+ const apiTestMode = process.env["OA_API_TEST_MODE"] === "1";
613513
613730
  const config = loadConfig();
613514
613731
  const ollamaUrl = options2.ollamaUrl ?? config.backendUrl;
613515
613732
  const cwd4 = process.cwd();
@@ -613529,31 +613746,37 @@ function startApiServer(options2 = {}) {
613529
613746
  taskMgr.onEviction((taskId) => {
613530
613747
  deleteAgentTaskSidecar(taskId);
613531
613748
  });
613532
- restoreAgentTasks(taskMgr).then((report2) => {
613533
- if (report2.restored > 0) {
613534
- log22(` task recovery: restored=${report2.restored} re-attached=${report2.reAttached} marked-failed=${report2.markedFailed}
613749
+ if (!apiTestMode) {
613750
+ restoreAgentTasks(taskMgr).then((report2) => {
613751
+ if (report2.restored > 0) {
613752
+ log22(` task recovery: restored=${report2.restored} re-attached=${report2.reAttached} marked-failed=${report2.markedFailed}
613535
613753
  `);
613536
- }
613537
- }).catch(() => {
613538
- });
613539
- setTimeout(() => {
613540
- try {
613541
- purgeOldSidecars(24);
613542
- } catch {
613543
- }
613544
- }, 5e3).unref();
613754
+ }
613755
+ }).catch(() => {
613756
+ });
613757
+ }
613758
+ if (!apiTestMode) {
613759
+ setTimeout(() => {
613760
+ try {
613761
+ purgeOldSidecars(24);
613762
+ } catch {
613763
+ }
613764
+ }, 5e3).unref();
613765
+ }
613545
613766
  } catch {
613546
613767
  }
613547
- try {
613548
- const report2 = loadPersistedSessions();
613549
- if (report2.restored > 0 || report2.staleInFlight > 0) {
613550
- log22(` chat sessions: restored=${report2.restored} stale-in-flight=${report2.staleInFlight}
613768
+ if (!apiTestMode) {
613769
+ try {
613770
+ const report2 = loadPersistedSessions();
613771
+ if (report2.restored > 0 || report2.staleInFlight > 0) {
613772
+ log22(` chat sessions: restored=${report2.restored} stale-in-flight=${report2.staleInFlight}
613551
613773
  `);
613774
+ }
613775
+ } catch {
613552
613776
  }
613553
- } catch {
613554
613777
  }
613555
613778
  setTodoEventPublisher(null);
613556
- try {
613779
+ if (!apiTestMode) try {
613557
613780
  const dir = todoDir();
613558
613781
  try {
613559
613782
  mkdirSync69(dir, { recursive: true });
@@ -613630,7 +613853,7 @@ function startApiServer(options2 = {}) {
613630
613853
  `);
613631
613854
  }
613632
613855
  const retentionDays = parseInt(process.env["OA_JOB_RETENTION_DAYS"] ?? "30", 10);
613633
- if (retentionDays > 0) {
613856
+ if (!apiTestMode && retentionDays > 0) {
613634
613857
  try {
613635
613858
  const jobsDir3 = join129(cwd4, ".oa", "jobs");
613636
613859
  if (existsSync113(jobsDir3)) {
@@ -613681,9 +613904,12 @@ function startApiServer(options2 = {}) {
613681
613904
  }
613682
613905
  let _accessRejected = 0;
613683
613906
  const handler = (req2, res) => {
613907
+ const requestId = req2.headers["x-request-id"] || randomUUID16();
613908
+ res.setHeader("X-Request-ID", requestId);
613909
+ res.setHeader("X-API-Version", API_VERSION);
613684
613910
  const rawIp = req2.socket?.remoteAddress ?? "";
613685
613911
  const hasBearer = typeof req2.headers["authorization"] === "string" && req2.headers["authorization"].startsWith("Bearer ");
613686
- const anyKeyConfigured = Boolean(
613912
+ const anyKeyConfigured = hasBearer && Boolean(
613687
613913
  process.env["OA_API_KEY"] || process.env["OA_API_KEYS"] || runtimeKeysExist()
613688
613914
  );
613689
613915
  if (!isAllowedIP(rawIp, runtimeAccessMode) && !(hasBearer && anyKeyConfigured)) {
@@ -613770,10 +613996,6 @@ function startApiServer(options2 = {}) {
613770
613996
  handleEntitiesList(req2, res);
613771
613997
  return;
613772
613998
  }
613773
- if (req2.method === "POST" && url.pathname === "/v1/memory/search") {
613774
- handleMemorySearch2(req2, res);
613775
- return;
613776
- }
613777
613999
  } catch {
613778
614000
  }
613779
614001
  handleRequest(req2, res, ollamaUrl, verbose).catch((err) => {
@@ -614024,6 +614246,10 @@ function startApiServer(options2 = {}) {
614024
614246
  `);
614025
614247
  log22(` Primary: ${config.backendUrl} (${config.backendType || "ollama"})
614026
614248
  `);
614249
+ if (apiTestMode) {
614250
+ void refreshEndpointRegistry();
614251
+ return;
614252
+ }
614027
614253
  try {
614028
614254
  const { writeFileSync: writeFileSync67, mkdirSync: mkdirSync74, existsSync: _exists, readFileSync: _rfs } = require3("node:fs");
614029
614255
  const { join: _join } = require3("node:path");
@@ -614451,28 +614677,6 @@ async function handleEntitiesList(req2, res) {
614451
614677
  jsonResponse(res, 500, { error: "server_error", message: err instanceof Error ? err.message : String(err) });
614452
614678
  }
614453
614679
  }
614454
- async function handleMemorySearch2(req2, res) {
614455
- try {
614456
- const body = await parseJsonBody(req2);
614457
- if (!body || typeof body !== "object") {
614458
- jsonResponse(res, 400, { error: "bad_request" });
614459
- return;
614460
- }
614461
- const b = body;
614462
- const query = String(b.query || "");
614463
- const modality = b.modality ? String(b.modality) : void 0;
614464
- const limit = b.limit ? Math.max(1, Math.min(200, parseInt(String(b.limit), 10))) : 20;
614465
- const qEmb = Array.isArray(b.query_embedding) ? new Float32Array(b.query_embedding) : null;
614466
- const wLex = typeof b.lexical_weight === "number" ? b.lexical_weight : 1;
614467
- const wEmb = typeof b.embedding_weight === "number" ? b.embedding_weight : 1;
614468
- const { EpisodeStore: EpisodeStore3 } = await Promise.resolve().then(() => (init_dist7(), dist_exports2));
614469
- const epStore = new EpisodeStore3(join129(process.cwd(), ".oa", "memory.db"));
614470
- const results = epStore.search({ query, modality, limit }, { queryEmbedding: qEmb, lexicalWeight: wLex, embeddingWeight: wEmb });
614471
- jsonResponse(res, 200, { object: "list", data: results.map((e2) => ({ id: e2.id, modality: e2.modality, content: e2.content, timestamp: e2.timestamp })) });
614472
- } catch (err) {
614473
- jsonResponse(res, 500, { error: "server_error", message: err instanceof Error ? err.message : String(err) });
614474
- }
614475
- }
614476
614680
  async function apiServeCommand(opts, config) {
614477
614681
  const server2 = startApiServer({
614478
614682
  port: opts.port,
@@ -614533,7 +614737,7 @@ function setTimerEnabled(name10, enabled2) {
614533
614737
  return false;
614534
614738
  }
614535
614739
  }
614536
- var require3, endpointRegistry, modelRouteMap, endpointUsage, _lastEndpointDiagnostics, BACKEND_TIMEOUT_DEFAULT_MS, BACKEND_TIMEOUT_MAX_MS, metrics, startedAt, runningProcesses, perKeyUsage, CRON_MARKER2;
614740
+ var require3, endpointRegistry, modelRouteMap, endpointUsage, _lastEndpointDiagnostics, BACKEND_TIMEOUT_DEFAULT_MS, BACKEND_TIMEOUT_MAX_MS, MODEL_LIST_TIMEOUT_DEFAULT_MS, metrics, startedAt, runningProcesses, perKeyUsage, CRON_MARKER2;
614537
614741
  var init_serve = __esm({
614538
614742
  "packages/cli/src/api/serve.ts"() {
614539
614743
  "use strict";
@@ -614569,6 +614773,7 @@ var init_serve = __esm({
614569
614773
  _lastEndpointDiagnostics = [];
614570
614774
  BACKEND_TIMEOUT_DEFAULT_MS = 12e4;
614571
614775
  BACKEND_TIMEOUT_MAX_MS = 36e5;
614776
+ MODEL_LIST_TIMEOUT_DEFAULT_MS = 1500;
614572
614777
  metrics = {
614573
614778
  requests: /* @__PURE__ */ new Map(),
614574
614779
  totalTokensIn: 0,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.578",
3
+ "version": "0.187.580",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.578",
9
+ "version": "0.187.580",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.578",
3
+ "version": "0.187.580",
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",