open-agents-ai 0.187.277 → 0.187.280

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
@@ -264717,12 +264717,12 @@ var init_taskNormalizer = __esm({
264717
264717
  // packages/orchestrator/dist/dispatcher.js
264718
264718
  function estimateComplexity(task) {
264719
264719
  const text = `${task.goal} ${task.constraints.join(" ")} ${task.successCriteria.join(" ")}`;
264720
- const wordCount = text.split(/\s+/).length;
264720
+ const wordCount2 = text.split(/\s+/).length;
264721
264721
  if (task.urgency === "critical")
264722
264722
  return "high";
264723
- if (wordCount > 150 || task.successCriteria.length > 5)
264723
+ if (wordCount2 > 150 || task.successCriteria.length > 5)
264724
264724
  return "high";
264725
- if (wordCount > 60 || task.successCriteria.length > 2)
264725
+ if (wordCount2 > 60 || task.successCriteria.length > 2)
264726
264726
  return "medium";
264727
264727
  return "low";
264728
264728
  }
@@ -265643,8 +265643,8 @@ function classifyQuery(query) {
265643
265643
  return "error";
265644
265644
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed))
265645
265645
  return "symbol";
265646
- const wordCount = trimmed.split(/\s+/).length;
265647
- if (wordCount <= 3)
265646
+ const wordCount2 = trimmed.split(/\s+/).length;
265647
+ if (wordCount2 <= 3)
265648
265648
  return "short";
265649
265649
  return "long";
265650
265650
  }
@@ -270703,10 +270703,10 @@ Integrate this guidance into your current approach. Continue working on the task
270703
270703
  const turnTier = this.options.modelTier ?? "large";
270704
270704
  if (turn === 0 && (turnTier === "small" || turnTier === "medium")) {
270705
270705
  const goal = this._taskState.goal || "";
270706
- const wordCount = goal.split(/\s+/).length;
270706
+ const wordCount2 = goal.split(/\s+/).length;
270707
270707
  const hasMultipleActions = /\band\b.*\band\b|then.*then|also.*also/i.test(goal);
270708
270708
  const hasMultipleFiles = /files?.*files?|\.ts.*\.ts|create.*write|modify.*create/i.test(goal);
270709
- const isComplex = wordCount > 40 || hasMultipleActions || hasMultipleFiles;
270709
+ const isComplex = wordCount2 > 40 || hasMultipleActions || hasMultipleFiles;
270710
270710
  if (isComplex) {
270711
270711
  messages2.push({
270712
270712
  role: "user",
@@ -294676,6 +294676,8 @@ var init_voice = __esm({
294676
294676
  currentPlayback = null;
294677
294677
  speakQueue = [];
294678
294678
  speaking = false;
294679
+ drainPromise = null;
294680
+ drainResolve = null;
294679
294681
  phonemizeFn = null;
294680
294682
  /** True when current model uses MLX Audio backend */
294681
294683
  mlxActive = false;
@@ -294992,6 +294994,15 @@ var init_voice = __esm({
294992
294994
  const speedFactor = emotion ? emotionToSpeedFactor(emotion, this.starkMode, this.autistMode) : 1;
294993
294995
  this.enqueueSpeech(text, 0.55, 1 + pitchBias, speedFactor, 0.15);
294994
294996
  }
294997
+ /** Wait until the speak queue is fully drained (all audio played). */
294998
+ async waitUntilIdle() {
294999
+ if (!this.speaking && this.speakQueue.length === 0) return;
295000
+ if (this.drainPromise) {
295001
+ await this.drainPromise;
295002
+ } else {
295003
+ await this.sleep(100);
295004
+ }
295005
+ }
294995
295006
  enqueueSpeech(text, volume, pitchFactor, speedFactor = 1, stereoDelayMs = 0.6) {
294996
295007
  if (!this.enabled || !this.ready) return;
294997
295008
  text = sanitizeForTTS(text);
@@ -295190,6 +295201,11 @@ var init_voice = __esm({
295190
295201
  */
295191
295202
  async drainQueue() {
295192
295203
  this.speaking = true;
295204
+ if (!this.drainPromise) {
295205
+ this.drainPromise = new Promise((resolve40) => {
295206
+ this.drainResolve = resolve40;
295207
+ });
295208
+ }
295193
295209
  let isFirst = true;
295194
295210
  let prefetchedWav = null;
295195
295211
  while (this.speakQueue.length > 0) {
@@ -295241,6 +295257,14 @@ var init_voice = __esm({
295241
295257
  }
295242
295258
  }
295243
295259
  this.speaking = false;
295260
+ if (this.drainResolve) {
295261
+ try {
295262
+ this.drainResolve();
295263
+ } catch {
295264
+ }
295265
+ }
295266
+ this.drainResolve = null;
295267
+ this.drainPromise = null;
295244
295268
  }
295245
295269
  sleep(ms) {
295246
295270
  return new Promise((resolve40) => setTimeout(resolve40, ms));
@@ -328218,15 +328242,62 @@ __export(voicechat_exports, {
328218
328242
  VoiceChatSession: () => VoiceChatSession
328219
328243
  });
328220
328244
  import { EventEmitter as EventEmitter10 } from "node:events";
328221
- var VAD_SILENCE_MS, MAX_SEGMENT_MS, SUMMARY_INJECTION_INTERVAL, MAX_CONTEXT_TURNS, SYSTEM_PROMPT2, VoiceChatSession;
328245
+ function clamp01(x) {
328246
+ return x < 0 ? 0 : x > 1 ? 1 : x;
328247
+ }
328248
+ function alnumRatio(s2) {
328249
+ if (!s2) return 0;
328250
+ const al = (s2.match(/[\p{L}\p{N}]/gu) || []).length;
328251
+ return al / s2.length;
328252
+ }
328253
+ function wordCount(s2) {
328254
+ const words = s2.trim().match(/[\p{L}\p{N}][\p{L}\p{N}'’_-]*/gu);
328255
+ return words ? words.length : 0;
328256
+ }
328257
+ function repeatingCharPenalty(s2) {
328258
+ let maxRun = 1, cur = 1;
328259
+ for (let i2 = 1; i2 < s2.length; i2++) {
328260
+ if (s2[i2] === s2[i2 - 1]) cur++;
328261
+ else {
328262
+ if (cur > maxRun) maxRun = cur;
328263
+ cur = 1;
328264
+ }
328265
+ }
328266
+ if (cur > maxRun) maxRun = cur;
328267
+ return Math.min(1, Math.max(0, (maxRun - 3) / 10));
328268
+ }
328269
+ function computeSignalFromText(text, confidence) {
328270
+ const t2 = text.trim();
328271
+ if (!t2) return 0;
328272
+ if (NOISE_ONLY_RE.test(t2)) return 0.05;
328273
+ const len = t2.length;
328274
+ const wc = wordCount(t2);
328275
+ const alpha = alnumRatio(t2);
328276
+ let score = 0;
328277
+ if (wc >= 6 && alpha >= 0.6) score = 0.85;
328278
+ else if (wc >= 3 && alpha >= 0.5) score = 0.7;
328279
+ else if (wc >= 2 && alpha >= 0.4) score = 0.5;
328280
+ else if (wc >= 1 && alpha >= 0.3 && len >= 4) score = 0.35;
328281
+ else score = 0.15;
328282
+ score -= repeatingCharPenalty(t2) * 0.4;
328283
+ if (typeof confidence === "number" && !Number.isNaN(confidence)) {
328284
+ score = 0.7 * score + 0.3 * clamp01(confidence);
328285
+ }
328286
+ return clamp01(score);
328287
+ }
328288
+ function truncateForLog(s2, n2) {
328289
+ return s2.length <= n2 ? s2 : s2.slice(0, n2 - 1) + "…";
328290
+ }
328291
+ var VAD_SILENCE_MS, MAX_SEGMENT_MS, MAX_CONTEXT_TURNS, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
328222
328292
  var init_voicechat = __esm({
328223
328293
  "packages/cli/src/tui/voicechat.ts"() {
328224
328294
  "use strict";
328225
- VAD_SILENCE_MS = 1100;
328295
+ VAD_SILENCE_MS = 2e3;
328226
328296
  MAX_SEGMENT_MS = 6500;
328227
- SUMMARY_INJECTION_INTERVAL = 4;
328228
328297
  MAX_CONTEXT_TURNS = 20;
328229
328298
  SYSTEM_PROMPT2 = `You are a voice assistant having a live spoken conversation. Keep responses extremely brief — 1-2 sentences max. You're speaking aloud, not writing. Be conversational, direct, and helpful. Don't use markdown, bullet points, or formatting — just natural speech. If you don't know something, say so briefly. Do not over-think — respond quickly and concisely.`;
328299
+ MIN_SIGNAL_SCORE = 0.4;
328300
+ NOISE_ONLY_RE = /^(?:[.·…\s,;:!?\-–—_()\[\]{}"'`]+|(?:uh|um|erm|hmm|mm+|uhh+|umm+)[\s.!?]*)+$/i;
328230
328301
  VoiceChatSession = class extends EventEmitter10 {
328231
328302
  voice;
328232
328303
  listen;
@@ -328245,6 +328316,7 @@ var init_voicechat = __esm({
328245
328316
  captureStartTime = 0;
328246
328317
  silenceTimer = null;
328247
328318
  maxSegmentTimer = null;
328319
+ lastSignalScore = null;
328248
328320
  // Abort control for inference
328249
328321
  abortController = null;
328250
328322
  // Callbacks
@@ -328256,6 +328328,7 @@ var init_voicechat = __esm({
328256
328328
  // Bound handlers for cleanup
328257
328329
  _onTranscript = null;
328258
328330
  _onError = null;
328331
+ _retryMicTimer = null;
328259
328332
  constructor(opts) {
328260
328333
  super();
328261
328334
  this.voice = opts.voice;
@@ -328307,20 +328380,37 @@ var init_voicechat = __esm({
328307
328380
  this._onTranscript = (...args) => {
328308
328381
  let text;
328309
328382
  let isFinal;
328383
+ let snr;
328384
+ let confidence;
328310
328385
  if (typeof args[0] === "object" && args[0] !== null) {
328311
328386
  const evt = args[0];
328312
328387
  text = evt.text ?? "";
328313
328388
  isFinal = evt.isFinal ?? false;
328389
+ snr = evt.snr;
328390
+ confidence = evt.confidence;
328314
328391
  } else {
328315
328392
  text = String(args[0] ?? "");
328316
328393
  isFinal = Boolean(args[1]);
328317
328394
  }
328318
328395
  if (!text.trim()) return;
328319
- this.handleTranscript(text.trim(), isFinal);
328396
+ this.handleTranscript(text.trim(), isFinal, snr, confidence);
328320
328397
  };
328321
328398
  this._onError = (err) => {
328322
328399
  const msg = err instanceof Error ? err.message : String(err);
328323
328400
  this.onStatus(`ASR error (voicechat continues without mic): ${msg.slice(0, 80)}`);
328401
+ if (this.active && !this._retryMicTimer) {
328402
+ this._retryMicTimer = setTimeout(async () => {
328403
+ this._retryMicTimer = null;
328404
+ if (!this.active) return;
328405
+ try {
328406
+ await this.listen.stop().catch(() => {
328407
+ });
328408
+ await this.listen.start();
328409
+ this.onStatus("Mic auto-recovered — LISTENING");
328410
+ } catch {
328411
+ }
328412
+ }, 1e3);
328413
+ }
328324
328414
  };
328325
328415
  this.listen.on("transcript", this._onTranscript);
328326
328416
  this.listen.on("error", this._onError);
@@ -328372,7 +328462,7 @@ var init_voicechat = __esm({
328372
328462
  // ---------------------------------------------------------------------------
328373
328463
  // Transcript handling — VAD-style segment capture (Voryn pattern)
328374
328464
  // ---------------------------------------------------------------------------
328375
- handleTranscript(text, isFinal) {
328465
+ handleTranscript(text, isFinal, snr, confidence) {
328376
328466
  if (!this.active) return;
328377
328467
  if (this._state !== "LISTENING" && this._state !== "CAPTURING") {
328378
328468
  return;
@@ -328388,6 +328478,8 @@ var init_voicechat = __esm({
328388
328478
  }, MAX_SEGMENT_MS);
328389
328479
  }
328390
328480
  this.captureBuffer = text;
328481
+ this.lastSignalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp01(snr) : computeSignalFromText(text, confidence);
328482
+ this.emit("snr", { score: this.lastSignalScore });
328391
328483
  this.onPartialTranscript(text);
328392
328484
  if (this.silenceTimer) clearTimeout(this.silenceTimer);
328393
328485
  if (isFinal) {
@@ -328418,10 +328510,25 @@ var init_voicechat = __esm({
328418
328510
  this.setState("LISTENING");
328419
328511
  return;
328420
328512
  }
328513
+ const score = this.lastSignalScore ?? computeSignalFromText(text);
328514
+ if (score < MIN_SIGNAL_SCORE || NOISE_ONLY_RE.test(text)) {
328515
+ this.onStatus(`Ignoring low-signal utterance (SNR:${score.toFixed(2)}): ${truncateForLog(text, 48)}`);
328516
+ this.emit("snrFiltered", { score, text });
328517
+ this.setState("LISTENING");
328518
+ this.captureBuffer = "";
328519
+ this.lastSignalScore = null;
328520
+ return;
328521
+ }
328421
328522
  this.setState("TRANSCRIBING");
328422
328523
  this.onUserSpeech(text);
328423
328524
  this.context.push({ role: "user", content: text });
328424
328525
  this.turnCount++;
328526
+ if (this.runner) {
328527
+ try {
328528
+ this.runner.injectUserMessage(`[VOICECHAT] ${text}`);
328529
+ } catch {
328530
+ }
328531
+ }
328425
328532
  while (this.context.length > MAX_CONTEXT_TURNS + 1) {
328426
328533
  this.context.splice(1, 1);
328427
328534
  }
@@ -328443,11 +328550,18 @@ var init_voicechat = __esm({
328443
328550
  this.setState("SPEAKING");
328444
328551
  this.onAgentSpeech(response.trim());
328445
328552
  this.voice.speak(response.trim());
328446
- if (this.runner && this.turnCount % SUMMARY_INJECTION_INTERVAL === 0) {
328553
+ if (this.runner) {
328447
328554
  this.injectSummary();
328448
328555
  }
328449
- const estimatedMs = Math.max(1500, response.length / 5 * (6e4 / 150));
328450
- await new Promise((r2) => setTimeout(r2, estimatedMs));
328556
+ if (typeof this.voice.waitUntilIdle === "function") {
328557
+ try {
328558
+ await this.voice.waitUntilIdle();
328559
+ } catch {
328560
+ }
328561
+ } else {
328562
+ const estimatedMs = Math.max(1500, response.length / 5 * (6e4 / 150));
328563
+ await new Promise((r2) => setTimeout(r2, estimatedMs));
328564
+ }
328451
328565
  }
328452
328566
  } catch (err) {
328453
328567
  if (!this.active) return;
@@ -328570,13 +328684,19 @@ var init_voicechat = __esm({
328570
328684
  // ---------------------------------------------------------------------------
328571
328685
  injectSummary() {
328572
328686
  if (!this.runner) return;
328573
- const recentTurns = this.context.filter((t2) => t2.role !== "system").slice(-6).map((t2) => `${t2.role === "user" ? "User" : "Assistant"}: ${t2.content}`).join("\n");
328687
+ const recentTurns = this.context.filter((t2) => t2.role !== "system").slice(-8).map((t2) => `${t2.role === "user" ? "User" : "Assistant"}: ${t2.content}`).join("\n");
328574
328688
  this.runner.injectUserMessage(
328575
- `[VOICECHAT SUMMARY] The following is a summary of the recent voice conversation happening in parallel. You don't need to respond to this directly — it's for your awareness. Continue your current task.
328689
+ `[VOICECHAT SUMMARY] Parallel voice liaison update (for awareness only). Continue your current task; do not respond to this directly.
328576
328690
 
328577
328691
  ${recentTurns}`
328578
328692
  );
328579
328693
  }
328694
+ /** Enqueue narration from main agent events into the voice channel */
328695
+ enqueueAgentNarration(text, subordinate = true) {
328696
+ if (!text || !this.active) return;
328697
+ if (subordinate) this.voice.speakSubordinate(text);
328698
+ else this.voice.speak(text);
328699
+ }
328580
328700
  };
328581
328701
  }
328582
328702
  });
@@ -330156,6 +330276,17 @@ ${entry.fullContent}`
330156
330276
  }
330157
330277
  });
330158
330278
  }
330279
+ if (voice?.enabled && voice.voiceMode === "voicechat" && _voiceChatSession?.isActive && event.toolName === "task_complete") {
330280
+ const emoStateFinal = emotionEngine?.getState();
330281
+ const emoCtxFinal = emoStateFinal ? { valence: emoStateFinal.valence, arousal: emoStateFinal.arousal, label: emoStateFinal.label, emoji: emoStateFinal.emoji } : void 0;
330282
+ const desc = describeTaskComplete(String(event.content ?? ""), true, vLevel);
330283
+ if (desc) {
330284
+ try {
330285
+ _voiceChatSession.enqueueAgentNarration(desc, false);
330286
+ } catch {
330287
+ }
330288
+ }
330289
+ }
330159
330290
  break;
330160
330291
  }
330161
330292
  case "model_response":
@@ -330237,6 +330368,15 @@ ${entry.fullContent}`
330237
330368
  voice.speak(chatText);
330238
330369
  voice._spokenStreamText = true;
330239
330370
  }
330371
+ } else if (voice?.enabled && voice.voiceMode === "voicechat" && (streamTextBuffer || event.content)) {
330372
+ const chatText = (streamTextBuffer || event.content || "").trim();
330373
+ streamTextBuffer = "";
330374
+ if (chatText.length > 10 && _voiceChatSession?.isActive) {
330375
+ try {
330376
+ _voiceChatSession.enqueueAgentNarration(chatText, false);
330377
+ } catch {
330378
+ }
330379
+ }
330240
330380
  }
330241
330381
  break;
330242
330382
  }
@@ -331274,7 +331414,7 @@ ${opts.systemPromptAddition}` : `Working directory: ${repoRoot}`;
331274
331414
  autoUpdateTimer.unref();
331275
331415
  const voiceEngine = new VoiceEngine();
331276
331416
  let voiceSession = null;
331277
- let _voiceChatSession = null;
331417
+ let _voiceChatSession2 = null;
331278
331418
  let tunnelGateway = null;
331279
331419
  let p2pGateway = null;
331280
331420
  let peerMesh = null;
@@ -332963,7 +333103,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
332963
333103
  },
332964
333104
  // --- /voicechat: Voryn-style state machine voice conversation ---
332965
333105
  async voiceChatStart() {
332966
- if (_voiceChatSession?.isActive) return;
333106
+ if (_voiceChatSession2?.isActive) return;
332967
333107
  if (!voiceEngine.enabled || !voiceEngine.ready) {
332968
333108
  writeContent(() => renderInfo2("Auto-enabling voice for voice chat..."));
332969
333109
  const voiceMsg = await voiceEngine.toggle();
@@ -332980,7 +333120,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
332980
333120
  }
332981
333121
  }
332982
333122
  };
332983
- _voiceChatSession = new VoiceChatSession2({
333123
+ _voiceChatSession2 = new VoiceChatSession2({
332984
333124
  voice: voiceEngine,
332985
333125
  listen: listenEng,
332986
333126
  backendUrl: currentConfig.backendUrl,
@@ -333005,16 +333145,28 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
333005
333145
  writeContent(() => renderInfo2(`\x1B[38;5;243m[voicechat] ${state}\x1B[0m`));
333006
333146
  }
333007
333147
  });
333008
- await _voiceChatSession.start();
333148
+ _voiceChatSession2.on("snr", (e2) => {
333149
+ const s2 = typeof e2?.score === "number" ? Math.max(0, Math.min(1, e2.score)) : null;
333150
+ if (s2 !== null) {
333151
+ writeContent(() => {
333152
+ process.stdout.write(`\r\x1B[2K\x1B[38;5;243m [hearing] (snr:${s2.toFixed(2)})\x1B[0m`);
333153
+ });
333154
+ }
333155
+ });
333156
+ _voiceChatSession2.on("snrFiltered", (e2) => {
333157
+ const s2 = typeof e2?.score === "number" ? e2.score.toFixed(2) : "?";
333158
+ writeContent(() => renderInfo2(`\x1B[38;5;243m[voicechat]\x1B[0m dropped low-signal utterance (SNR:${s2})`));
333159
+ });
333160
+ await _voiceChatSession2.start();
333009
333161
  },
333010
333162
  async voiceChatStop() {
333011
- if (_voiceChatSession?.isActive) {
333012
- await _voiceChatSession.stop();
333013
- _voiceChatSession = null;
333163
+ if (_voiceChatSession2?.isActive) {
333164
+ await _voiceChatSession2.stop();
333165
+ _voiceChatSession2 = null;
333014
333166
  }
333015
333167
  },
333016
333168
  isVoiceChatActive() {
333017
- return _voiceChatSession?.isActive ?? false;
333169
+ return _voiceChatSession2?.isActive ?? false;
333018
333170
  },
333019
333171
  async exposeStart(kindOrUrl, authKey, transport, fullAccess, passthrough, loadbalance) {
333020
333172
  const knownKinds = ["ollama", "vllm", "llvm", "passthrough"];
package/dist/launcher.cjs CHANGED
@@ -1,14 +1,78 @@
1
1
  #!/usr/bin/env node
2
- // Minimal launcher shim for open-agents CLI — forwards to bundled index.js
3
- // Use dynamic import() so ESM with top-level await can load correctly.
4
- (async () => {
2
+ // Robust launcher for open-agents CLI.
3
+ // - Runs the ESM entry as a child process
4
+ // - On exit code 120 (update), resets terminal and restarts child
5
+ // - Prevents raw-mode/mouse-tracking bleedthrough on restart or crash
6
+
7
+ const { spawn, spawnSync } = require('node:child_process');
8
+ const { resolve } = require('node:path');
9
+
10
+ function resetTerminal() {
11
+ try { if (process.stdin.isTTY && typeof process.stdin.setRawMode === 'function') process.stdin.setRawMode(false); } catch {}
12
+ // Disable mouse tracking, bracketed paste, show cursor, reset attrs, exit alt screen if active
13
+ const ESC = '\x1B';
5
14
  try {
6
- const { resolve } = require('node:path');
7
- const { pathToFileURL } = require('node:url');
8
- const entryUrl = pathToFileURL(resolve(__dirname, 'index.js')).href;
9
- await import(entryUrl);
10
- } catch (e) {
11
- console.error('Failed to launch open-agents:', e && e.message ? e.message : e);
12
- process.exit(1);
15
+ process.stdout.write(
16
+ ESC + '[?25h' + // show cursor
17
+ ESC + '[?1000l' + // X10 mouse off
18
+ ESC + '[?1002l' + // button-event mouse off
19
+ ESC + '[?1003l' + // any-event mouse off
20
+ ESC + '[?1006l' + // SGR mouse off
21
+ ESC + '[?1015l' + // urxvt mouse off
22
+ ESC + '[?2004l' + // bracketed paste off
23
+ ESC + '[?1049l' + // exit alt screen
24
+ ESC + '[0m' // reset attributes
25
+ );
26
+ } catch {}
27
+ // stty sane (POSIX)
28
+ if (process.platform !== 'win32' && process.stdin.isTTY) {
29
+ try { spawnSync('stty', ['sane'], { stdio: 'inherit' }); } catch {}
30
+ }
31
+ }
32
+
33
+ function runChild() {
34
+ const entry = resolve(__dirname, 'index.js');
35
+ const args = [entry, ...process.argv.slice(2)];
36
+ const child = spawn(process.execPath, args, {
37
+ stdio: 'inherit',
38
+ env: process.env,
39
+ });
40
+ return child;
41
+ }
42
+
43
+ (async () => {
44
+ let restarts = 0;
45
+ const MAX_RESTARTS = 3;
46
+ let child = runChild();
47
+
48
+ const forward = (sig) => {
49
+ try { child && child.kill(sig); } catch {}
50
+ };
51
+ process.on('SIGINT', () => forward('SIGINT'));
52
+ process.on('SIGTERM', () => forward('SIGTERM'));
53
+
54
+ function attach(childProc) {
55
+ childProc.on('exit', (code, signal) => {
56
+ if (signal) {
57
+ resetTerminal();
58
+ process.kill(process.pid, signal);
59
+ return;
60
+ }
61
+ if (code === 120 && restarts < MAX_RESTARTS) {
62
+ // Update-triggered restart
63
+ resetTerminal();
64
+ restarts += 1;
65
+ setTimeout(() => {
66
+ child = runChild();
67
+ attach(child);
68
+ }, 300);
69
+ return;
70
+ }
71
+ // Normal exit or too many restarts
72
+ resetTerminal();
73
+ process.exit(typeof code === 'number' ? code : 0);
74
+ });
13
75
  }
76
+
77
+ attach(child);
14
78
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.277",
3
+ "version": "0.187.280",
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",