open-agents-ai 0.103.83 → 0.103.86

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 -59
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4713,6 +4713,24 @@ Process error: ${err.message}`;
4713
4713
  entry.finishedAt = Date.now();
4714
4714
  return true;
4715
4715
  }
4716
+ /** Kill ALL running tasks immediately (for /stop and /quit authority) */
4717
+ stopAll() {
4718
+ let killed = 0;
4719
+ for (const [, entry] of this.tasks) {
4720
+ if (entry.status !== "running")
4721
+ continue;
4722
+ if (entry.child) {
4723
+ try {
4724
+ entry.child.kill("SIGKILL");
4725
+ } catch {
4726
+ }
4727
+ }
4728
+ entry.status = "stopped";
4729
+ entry.finishedAt = Date.now();
4730
+ killed++;
4731
+ }
4732
+ return killed;
4733
+ }
4716
4734
  list() {
4717
4735
  return Array.from(this.tasks.values()).map(toInfo);
4718
4736
  }
@@ -12442,8 +12460,8 @@ async function handleCmd(cmd) {
12442
12460
  // succeeds in <1s. Racing them eliminates the wait.
12443
12461
  dlog('remote_infer: invoking ' + riCapName + ' on peer ' + riTargetPeer.slice(0, 20) + ' (libp2p' + (_natsConn ? ' + NATS parallel' : '') + ')');
12444
12462
  try {
12445
- var RI_TIMEOUT = 300000;
12446
- var riInvokeP = nexus.invokeCapability(riTargetPeer, riCapName, riData, { stream: false, maxDurationMs: 240000 });
12463
+ var RI_TIMEOUT = 60000; // 60s outer timeout \u2014 fail fast for interactive use
12464
+ var riInvokeP = nexus.invokeCapability(riTargetPeer, riCapName, riData, { stream: false, maxDurationMs: 45000 });
12447
12465
  var riTimeoutP = new Promise(function(_, reject) { setTimeout(function() { reject(new Error('remote_infer timed out after ' + (RI_TIMEOUT / 1000) + 's')); }, RI_TIMEOUT); });
12448
12466
 
12449
12467
  // Build race candidates \u2014 libp2p + timeout always present
@@ -12454,7 +12472,7 @@ async function handleCmd(cmd) {
12454
12472
  var _riNatsRaceP = (async function() {
12455
12473
  var _rSubject = 'nexus.invoke.' + riTargetPeer;
12456
12474
  var _rPayload = JSON.stringify({ capability: riCapName, input: riData, from: nexus.peerId || '' });
12457
- var _rResp = await _natsConn.request(_rSubject, _natsCodec.encode(_rPayload), { timeout: 240000 });
12475
+ var _rResp = await _natsConn.request(_rSubject, _natsCodec.encode(_rPayload), { timeout: 45000 });
12458
12476
  var _rResult = JSON.parse(_natsCodec.decode(_rResp.data));
12459
12477
  if (_rResult && _rResult.error) throw new Error('NATS: ' + _rResult.error);
12460
12478
  dlog('remote_infer: NATS won the race');
@@ -12493,8 +12511,8 @@ async function handleCmd(cmd) {
12493
12511
  var rPid = riCandidates[ri];
12494
12512
  dlog('remote_infer: trying peer ' + rPid.slice(0, 20) + '...');
12495
12513
  try {
12496
- var PEER_INVOKE_TIMEOUT = 180000; // 3 min per peer \u2014 remote API inference can be slow
12497
- var specInvoke = nexus.invokeCapability(rPid, riCapName, riData, { stream: false, maxDurationMs: 150000 });
12514
+ var PEER_INVOKE_TIMEOUT = 30000; // 30s per peer in discovery \u2014 fail fast, try next
12515
+ var specInvoke = nexus.invokeCapability(rPid, riCapName, riData, { stream: false, maxDurationMs: 25000 });
12498
12516
  var specTimeout = new Promise(function(_, reject) {
12499
12517
  setTimeout(function() { reject(new Error('peer invoke timeout')); }, PEER_INVOKE_TIMEOUT);
12500
12518
  });
@@ -12523,7 +12541,7 @@ async function handleCmd(cmd) {
12523
12541
  try {
12524
12542
  var _dpSubject = 'nexus.invoke.' + _dpPid;
12525
12543
  var _dpPayload = JSON.stringify({ capability: riCapName, input: riData, from: nexus.peerId || '' });
12526
- var _dpResp = await _natsConn.request(_dpSubject, _natsCodec.encode(_dpPayload), { timeout: 180000 });
12544
+ var _dpResp = await _natsConn.request(_dpSubject, _natsCodec.encode(_dpPayload), { timeout: 30000 });
12527
12545
  var _dpResult = JSON.parse(_natsCodec.decode(_dpResp.data));
12528
12546
  if (_dpResult && !_dpResult.error) {
12529
12547
  riResult = _dpResult;
@@ -18848,6 +18866,13 @@ ${this.options.dynamicContext}`,
18848
18866
  injectUserMessage(content) {
18849
18867
  this.pendingUserMessages.push(content);
18850
18868
  }
18869
+ /** Retract the last pending user message (Esc cancel).
18870
+ * Returns the retracted text, or null if queue is empty or already consumed. */
18871
+ retractLastPendingMessage() {
18872
+ if (this.pendingUserMessages.length === 0)
18873
+ return null;
18874
+ return this.pendingUserMessages.pop();
18875
+ }
18851
18876
  /** Abort the current task run — cancels in-flight requests and kills child processes */
18852
18877
  abort() {
18853
18878
  this.aborted = true;
@@ -19126,6 +19151,10 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
19126
19151
  try {
19127
19152
  response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
19128
19153
  } catch (reqErr) {
19154
+ if (reqErr instanceof Error && reqErr.fatal) {
19155
+ this.emit({ type: "error", content: `Backend error: ${reqErr.message}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
19156
+ break;
19157
+ }
19129
19158
  const recovered = await this.retryOnTransient(reqErr, chatRequest, turn);
19130
19159
  if (!recovered) {
19131
19160
  const errMsg = reqErr instanceof Error ? reqErr.message : String(reqErr);
@@ -20886,6 +20915,8 @@ ${transcript}`
20886
20915
  // -------------------------------------------------------------------------
20887
20916
  /** Detect whether an error is transient (worth retrying) */
20888
20917
  isTransientError(err) {
20918
+ if (err instanceof Error && err.fatal)
20919
+ return false;
20889
20920
  const msg = err instanceof Error ? err.message : String(err);
20890
20921
  if (/Backend HTTP (502|503|504)/i.test(msg))
20891
20922
  return true;
@@ -20895,6 +20926,10 @@ ${transcript}`
20895
20926
  return true;
20896
20927
  if (/model is loading|server busy|overloaded/i.test(msg))
20897
20928
  return true;
20929
+ if (/multiaddrs failed|all multiaddrs failed|dial to peer failed|stream was reset|Cannot reach peer/i.test(msg))
20930
+ return true;
20931
+ if (/NATS.*timeout|relay.*timeout|invoke.*timeout/i.test(msg))
20932
+ return true;
20898
20933
  return false;
20899
20934
  }
20900
20935
  /**
@@ -21222,6 +21257,15 @@ var init_nexusBackend = __esm({
21222
21257
  this.authKey = authKey || "";
21223
21258
  this.thinking = thinking ?? true;
21224
21259
  }
21260
+ /** Reset the consecutive failure counter (called on endpoint switch / reconnect) */
21261
+ resetFailures() {
21262
+ this.consecutiveFailures = 0;
21263
+ }
21264
+ /** Update target peer (allows switching peers without recreating the backend) */
21265
+ setTargetPeer(peerId) {
21266
+ this.targetPeer = peerId;
21267
+ this.consecutiveFailures = 0;
21268
+ }
21225
21269
  async chatCompletion(request) {
21226
21270
  if (this.consecutiveFailures >= _NexusAgenticBackend.MAX_CONSECUTIVE_FAILURES) {
21227
21271
  const err = new Error(`Remote peer unreachable after ${this.consecutiveFailures} attempts. Use /endpoint to switch to a local model or reconnect.`);
@@ -21258,7 +21302,10 @@ var init_nexusBackend = __esm({
21258
21302
  }
21259
21303
  if (parsed.success === false) {
21260
21304
  this.consecutiveFailures++;
21261
- throw new Error(`Remote inference failed: ${parsed.output || rawResult}`);
21305
+ const output = String(parsed.output || rawResult);
21306
+ const isDialErr = /multiaddrs failed|Cannot reach peer|dial to peer|timeout|ECONNREFUSED/i.test(output);
21307
+ const hint = isDialErr ? ` (${this.consecutiveFailures}/${_NexusAgenticBackend.MAX_CONSECUTIVE_FAILURES} failures \u2014 will stop after ${_NexusAgenticBackend.MAX_CONSECUTIVE_FAILURES})` : "";
21308
+ throw new Error(`Remote inference failed: ${output}${hint}`);
21262
21309
  }
21263
21310
  this.consecutiveFailures = 0;
21264
21311
  const invokeResult = parsed.result;
@@ -33531,9 +33578,9 @@ function tuiSelect(opts) {
33531
33578
  const first = findSelectable(0, 1);
33532
33579
  cursor = first >= 0 ? first : 0;
33533
33580
  }
33534
- const termRows = process.stdout.rows ?? 24;
33535
- const overhead = 6;
33536
- const maxVisible = opts.maxVisible ?? Math.max(3, termRows - overhead);
33581
+ const selectChrome = 3;
33582
+ const contentArea = opts.availableRows ?? (process.stdout.rows ?? 24);
33583
+ const maxVisible = opts.maxVisible ?? Math.max(3, contentArea - selectChrome);
33537
33584
  let scrollOffset = 0;
33538
33585
  let lastRenderedLines = 0;
33539
33586
  return new Promise((resolve31) => {
@@ -35178,7 +35225,8 @@ async function showConfigEditor(ctx) {
35178
35225
  rl: ctx.rl,
35179
35226
  skipKeys,
35180
35227
  renderRow: renderConfigRow,
35181
- onAction
35228
+ onAction,
35229
+ availableRows: ctx.availableContentRows?.()
35182
35230
  });
35183
35231
  if (result.confirmed && result.key) {
35184
35232
  const entry = entries.find((e) => e.key === result.key);
@@ -35251,7 +35299,8 @@ async function handleExposeConfig(ctx) {
35251
35299
  items,
35252
35300
  title: "Expose Configuration",
35253
35301
  rl: ctx.rl,
35254
- skipKeys
35302
+ skipKeys,
35303
+ availableRows: ctx.availableContentRows?.()
35255
35304
  });
35256
35305
  if (!result.confirmed || !result.key) {
35257
35306
  renderInfo("Expose config cancelled.");
@@ -35360,7 +35409,8 @@ async function showModelPicker(ctx, local = false) {
35360
35409
  title: "Select Model",
35361
35410
  rl: ctx.rl,
35362
35411
  // Skip header rows
35363
- skipKeys: ["__header_recent__", "__header_available__"]
35412
+ skipKeys: ["__header_recent__", "__header_available__"],
35413
+ availableRows: ctx.availableContentRows?.()
35364
35414
  });
35365
35415
  if (!result.confirmed || !result.key) {
35366
35416
  renderInfo("Model selection cancelled.");
@@ -35390,6 +35440,7 @@ async function handleEndpoint(arg, ctx, local = false) {
35390
35440
  activeKey: ctx.config.backendUrl,
35391
35441
  title: "Select Endpoint",
35392
35442
  rl: ctx.rl,
35443
+ availableRows: ctx.availableContentRows?.(),
35393
35444
  onDelete: (item, done) => {
35394
35445
  deleteUsageRecord("endpoint", item.key, ctx.repoRoot);
35395
35446
  done(true);
@@ -45592,8 +45643,11 @@ var init_status_bar = __esm({
45592
45643
  active = false;
45593
45644
  scrollRegionTop = 1;
45594
45645
  stdinHooked = false;
45595
- /** Track previous terminal rows to clear ghost separators on resize */
45646
+ /** Track previous terminal dimensions to clear ghost separators on resize */
45596
45647
  _prevTermRows = 0;
45648
+ _prevTermCols = 0;
45649
+ /** Debounce timer for resize events — prevents separator stacking during drag */
45650
+ _resizeTimer = null;
45597
45651
  /**
45598
45652
  * Depth-counted content write guard. Incremented by beginContentWrite(),
45599
45653
  * decremented by endContentWrite(). Footer is only redrawn and cursor
@@ -46040,6 +46094,7 @@ var init_status_bar = __esm({
46040
46094
  this.scrollRegionTop = scrollRegionTop ?? 1;
46041
46095
  this.active = true;
46042
46096
  this._prevTermRows = process.stdout.rows ?? 24;
46097
+ this._prevTermCols = process.stdout.columns ?? 80;
46043
46098
  this.applyScrollRegion();
46044
46099
  this.renderFooterAndPositionInput();
46045
46100
  this.hookStdin();
@@ -46077,24 +46132,56 @@ var init_status_bar = __esm({
46077
46132
  get reservedRows() {
46078
46133
  return this._currentFooterHeight;
46079
46134
  }
46080
- /** Handle terminal resize — reapply scroll region and redraw footer */
46135
+ /**
46136
+ * Usable content area height — the rows available for scroll content,
46137
+ * model lists, dashboards, etc. Accounts for:
46138
+ * - Header/carousel rows (scrollRegionTop)
46139
+ * - Footer rows (buffer + top separator + input lines + bottom separator + metrics)
46140
+ *
46141
+ * This is the accurate "how many rows can I draw in?" value that all
46142
+ * overlay UIs (tuiSelect, /expose dashboard, etc.) should use.
46143
+ */
46144
+ get availableContentRows() {
46145
+ const rows = process.stdout.rows ?? 24;
46146
+ const usable = rows - (this.scrollRegionTop - 1) - this._currentFooterHeight;
46147
+ return Math.max(3, usable);
46148
+ }
46149
+ /** Handle terminal resize — debounced to prevent separator stacking during drag */
46081
46150
  handleResize() {
46082
46151
  if (!this.active)
46083
46152
  return;
46153
+ if (this._resizeTimer)
46154
+ clearTimeout(this._resizeTimer);
46155
+ this._resizeTimer = setTimeout(() => {
46156
+ this._resizeTimer = null;
46157
+ this._handleResizeImmediate();
46158
+ }, 50);
46159
+ }
46160
+ /** Actual resize handler — called after debounce settles */
46161
+ _handleResizeImmediate() {
46162
+ if (!this.active)
46163
+ return;
46164
+ const prevRows = this._prevTermRows;
46165
+ const prevCols = this._prevTermCols;
46084
46166
  this.updateFooterHeight();
46085
46167
  const rows = process.stdout.rows ?? 24;
46086
- const prevRows = this._prevTermRows;
46168
+ const cols = process.stdout.columns ?? 80;
46087
46169
  this._prevTermRows = rows;
46170
+ this._prevTermCols = cols;
46088
46171
  const pos = this.rowPositions(rows);
46089
46172
  const w = getTermWidth();
46090
46173
  const sep = c2.dim("\u2500".repeat(w));
46091
- let clearGhosts = "";
46092
- if (prevRows > 0 && rows < prevRows) {
46093
- clearGhosts = "\x1B[1;" + rows + `r\x1B[${pos.bufferRow};1H\x1B[J`;
46174
+ let clearBuf = "";
46175
+ if (prevRows > 0 && (rows !== prevRows || cols !== prevCols)) {
46176
+ const wrapFactor = cols > 0 ? Math.ceil(prevCols / cols) : 1;
46177
+ const extraWrappedRows = Math.max(0, (wrapFactor - 1) * 3);
46178
+ const safetyMargin = extraWrappedRows + 2;
46179
+ const clearFrom = Math.max(1, pos.bufferRow - safetyMargin);
46180
+ clearBuf = "\x1B[1;" + rows + `r\x1B[${clearFrom};1H\x1B[J`;
46094
46181
  }
46095
46182
  if (this.writeDepth > 0) {
46096
46183
  const inputWrap = this.wrapInput(w);
46097
- let buf = clearGhosts + `\x1B[${this.scrollRegionTop};${pos.scrollEnd}r\x1B[?25l\x1B[?7l\x1B[${pos.bufferRow};1H\x1B[2K${this.buildBufferContent(w)}\x1B[${pos.topSepRow};1H\x1B[2K${sep}`;
46184
+ let buf = clearBuf + `\x1B[${this.scrollRegionTop};${pos.scrollEnd}r\x1B[?25l\x1B[?7l\x1B[${pos.bufferRow};1H\x1B[2K${this.buildBufferContent(w)}\x1B[${pos.topSepRow};1H\x1B[2K${sep}`;
46098
46185
  for (let i = 0; i < inputWrap.lines.length; i++) {
46099
46186
  const row = pos.inputStartRow + i;
46100
46187
  const prefix = i === 0 ? this.promptText : " ".repeat(this.promptWidth);
@@ -46103,8 +46190,8 @@ var init_status_bar = __esm({
46103
46190
  buf += `\x1B[${pos.bottomSepRow};1H\x1B[2K${sep}\x1B[${pos.metricsRow};1H\x1B[2K${this.buildMetricsLine()}\x1B[?7h\x1B[${pos.scrollEnd};1H`;
46104
46191
  process.stdout.write(buf);
46105
46192
  } else {
46106
- if (clearGhosts)
46107
- process.stdout.write(clearGhosts);
46193
+ if (clearBuf)
46194
+ process.stdout.write(clearBuf);
46108
46195
  this.applyScrollRegion();
46109
46196
  this.renderFooterAndPositionInput();
46110
46197
  }
@@ -47956,8 +48043,9 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
47956
48043
  let currentTaskType;
47957
48044
  let sessionFilesTouched = [];
47958
48045
  let sessionToolCallCount = 0;
48046
+ let lastSteeringInput = "";
48047
+ let lastSteeringRetracted = false;
47959
48048
  let restoredSessionContext = null;
47960
- let pendingSessionRestore = false;
47961
48049
  let sessionSudoPassword = null;
47962
48050
  let sudoPromptPending = false;
47963
48051
  const idlePrompt = `${c2.bold(c2.white("\u276F "))}`;
@@ -48268,6 +48356,7 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
48268
48356
  items,
48269
48357
  title: "Select one or more (Space to toggle, Enter to confirm)",
48270
48358
  rl,
48359
+ availableRows: statusBar.isActive ? statusBar.availableContentRows : void 0,
48271
48360
  renderRow: (item, focused, _isActive) => {
48272
48361
  const isSelected = selected.has(item.key);
48273
48362
  const marker = isSelected ? c2.green("\u2611") : focused ? c2.cyan("\u2610") : c2.dim("\u2610");
@@ -48299,7 +48388,8 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
48299
48388
  const result = await tuiSelect({
48300
48389
  items,
48301
48390
  title: "Select one option",
48302
- rl
48391
+ rl,
48392
+ availableRows: statusBar.isActive ? statusBar.availableContentRows : void 0
48303
48393
  });
48304
48394
  if (statusBar?.isActive)
48305
48395
  statusBar.endContentWrite();
@@ -49217,7 +49307,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
49217
49307
  if (!activeTask)
49218
49308
  return false;
49219
49309
  activeTask.runner.abort();
49220
- writeContent(() => renderInfo("Task aborted."));
49310
+ const bgKilled = taskManager.stopAll();
49311
+ writeContent(() => renderInfo(`Task aborted.${bgKilled > 0 ? ` ${bgKilled} background task(s) killed.` : ""}`));
49221
49312
  return true;
49222
49313
  },
49223
49314
  pauseTask() {
@@ -49256,6 +49347,9 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
49256
49347
  }, 100);
49257
49348
  return true;
49258
49349
  },
49350
+ availableContentRows() {
49351
+ return statusBar.isActive ? statusBar.availableContentRows : Math.max(3, (process.stdout.rows ?? 24) - 6);
49352
+ },
49259
49353
  destroyProject() {
49260
49354
  const oaPath = join50(repoRoot, OA_DIR);
49261
49355
  if (existsSync40(oaPath)) {
@@ -49309,28 +49403,41 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
49309
49403
  const lastTime = lastEntry.savedAt ? new Date(lastEntry.savedAt) : null;
49310
49404
  const timeAgo = lastTime ? formatTimeAgo(lastTime) : "unknown";
49311
49405
  const lastTask = lastEntry.task?.slice(0, 80) || "unknown";
49312
- setTimeout(() => {
49406
+ setTimeout(async () => {
49313
49407
  writeContent(() => {
49314
49408
  renderInfo(`Previous session found (${savedCtx.entries.length} entries, last active ${timeAgo})`);
49315
49409
  renderInfo(`Last task: ${lastTask}${lastEntry.task && lastEntry.task.length > 80 ? "..." : ""}`);
49316
- renderInfo(`Restore previous context? (y/n) [auto-restores in 15s]`);
49317
49410
  });
49318
- showPrompt();
49319
- pendingSessionRestore = true;
49320
- setTimeout(() => {
49321
- if (pendingSessionRestore) {
49322
- pendingSessionRestore = false;
49323
- const prompt = buildContextRestorePrompt(repoRoot);
49324
- if (prompt) {
49325
- restoredSessionContext = prompt;
49326
- const info = loadSessionContext(repoRoot);
49327
- writeContent(() => renderInfo(`Context auto-restored from ${info?.entries.length ?? 0} session(s).`));
49328
- } else {
49329
- writeContent(() => renderInfo("No context to restore. Starting fresh."));
49330
- }
49331
- showPrompt();
49332
- }
49411
+ let autoRestoreTimer = setTimeout(() => {
49412
+ autoRestoreTimer = null;
49333
49413
  }, 15e3);
49414
+ const result = await tuiSelect({
49415
+ items: [
49416
+ { key: "restore", label: "Restore previous context" },
49417
+ { key: "fresh", label: "Start fresh" }
49418
+ ],
49419
+ activeKey: "restore",
49420
+ title: "Restore previous session context?",
49421
+ rl,
49422
+ availableRows: statusBar.isActive ? statusBar.availableContentRows : void 0
49423
+ });
49424
+ if (autoRestoreTimer)
49425
+ clearTimeout(autoRestoreTimer);
49426
+ if (result.confirmed && result.key === "restore" || !result.confirmed && !autoRestoreTimer) {
49427
+ const prompt = buildContextRestorePrompt(repoRoot);
49428
+ if (prompt) {
49429
+ restoredSessionContext = prompt;
49430
+ const info = loadSessionContext(repoRoot);
49431
+ writeContent(() => renderInfo(`Context restored from ${info?.entries.length ?? 0} session(s).`));
49432
+ } else {
49433
+ writeContent(() => renderInfo("No context to restore. Starting fresh."));
49434
+ }
49435
+ } else if (result.confirmed && result.key === "fresh") {
49436
+ writeContent(() => renderInfo("Starting fresh."));
49437
+ } else {
49438
+ writeContent(() => renderInfo("Starting fresh."));
49439
+ }
49440
+ showPrompt();
49334
49441
  }, 150);
49335
49442
  }
49336
49443
  }
@@ -49453,22 +49560,6 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
49453
49560
  rl.on("line", (line) => {
49454
49561
  persistHistoryLine(line);
49455
49562
  const input = line.trim();
49456
- if (pendingSessionRestore) {
49457
- pendingSessionRestore = false;
49458
- const answer = input.toLowerCase();
49459
- if (answer === "y" || answer === "yes") {
49460
- const prompt = buildContextRestorePrompt(repoRoot);
49461
- if (prompt) {
49462
- restoredSessionContext = prompt;
49463
- const info = loadSessionContext(repoRoot);
49464
- writeContent(() => renderInfo(`Context restored from ${info?.entries.length ?? 0} session(s). Will be injected into your next task.`));
49465
- }
49466
- } else {
49467
- writeContent(() => renderInfo("Starting fresh."));
49468
- }
49469
- showPrompt();
49470
- return;
49471
- }
49472
49563
  if (!input) {
49473
49564
  if (pasteBuffer.length > 0) {
49474
49565
  flushPasteBuffer();
@@ -49523,6 +49614,7 @@ ${result.content.slice(0, 2e3)}${result.content.length > 2e3 ? "\n[truncated]" :
49523
49614
  if (activeTask) {
49524
49615
  activeTask.runner.abort();
49525
49616
  }
49617
+ taskManager.stopAll();
49526
49618
  if (blessEngine?.isActive)
49527
49619
  blessEngine.stop();
49528
49620
  if (telegramBridge?.isActive)
@@ -49592,6 +49684,10 @@ Execute this skill now. Follow the behavioral guidance above.`;
49592
49684
  const isImage = isImagePath(cleanPath) && existsSync40(resolve28(repoRoot, cleanPath));
49593
49685
  const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync40(resolve28(repoRoot, cleanPath));
49594
49686
  if (activeTask) {
49687
+ if (activeTask.runner.isPaused) {
49688
+ activeTask.runner.resume();
49689
+ statusBar.setProcessing(true);
49690
+ }
49595
49691
  if (isImage) {
49596
49692
  try {
49597
49693
  const imgPath = resolve28(repoRoot, cleanPath);
@@ -49619,8 +49715,16 @@ ${result.text}`;
49619
49715
  writeContent(() => renderUserInterrupt(`[Media: ${cleanPath}]`));
49620
49716
  }
49621
49717
  } else {
49718
+ const isReplacement = lastSteeringRetracted;
49719
+ lastSteeringInput = input;
49720
+ lastSteeringRetracted = false;
49622
49721
  const lineCount = input.split("\n").length;
49623
- if (lineCount > 1) {
49722
+ if (isReplacement) {
49723
+ const displayText2 = lineCount > 1 ? `[pasted ${lineCount} lines]` : input;
49724
+ writeContent(() => process.stdout.write(`
49725
+ ${c2.green("\u21BB")} ${c2.bold("Context replaced:")} ${displayText2}
49726
+ `));
49727
+ } else if (lineCount > 1) {
49624
49728
  writeContent(() => renderUserInterrupt(`[pasted ${lineCount} lines]`));
49625
49729
  } else {
49626
49730
  writeContent(() => renderUserInterrupt(input));
@@ -49991,6 +50095,7 @@ ${c2.dim("(paste cancelled)")}
49991
50095
  }
49992
50096
  if (activeTask) {
49993
50097
  activeTask.runner.abort();
50098
+ taskManager.stopAll();
49994
50099
  writeContent(() => renderTaskAborted());
49995
50100
  } else {
49996
50101
  writeContent(() => process.stdout.write(`
@@ -49999,6 +50104,42 @@ ${c2.dim("(Use /quit to exit)")}
49999
50104
  }
50000
50105
  showPrompt();
50001
50106
  });
50107
+ if (process.stdin.isTTY) {
50108
+ readline2.emitKeypressEvents(process.stdin, rl);
50109
+ process.stdin.on("keypress", (_str, key) => {
50110
+ if (!key)
50111
+ return;
50112
+ if (key.name === "escape" && activeTask) {
50113
+ if (!activeTask.runner.isPaused) {
50114
+ activeTask.runner.pause();
50115
+ statusBar.setProcessing(false);
50116
+ }
50117
+ const retracted = activeTask.runner.retractLastPendingMessage();
50118
+ if (retracted) {
50119
+ lastSteeringInput = retracted;
50120
+ lastSteeringRetracted = true;
50121
+ writeContent(() => process.stdout.write(` ${c2.yellow("\u23F8")} ${c2.dim("\x1B[9m" + retracted.slice(0, 120) + "\x1B[29m")} ${c2.dim("\u2190 retracted, edit below and press Enter")}
50122
+ `));
50123
+ rl.line = retracted;
50124
+ rl.cursor = retracted.length;
50125
+ if (statusBar.isActive)
50126
+ statusBar.renderFooterPreserveCursor?.();
50127
+ } else if (lastSteeringInput) {
50128
+ lastSteeringRetracted = true;
50129
+ writeContent(() => process.stdout.write(` ${c2.yellow("\u23F8")} ${c2.dim("\x1B[9m" + lastSteeringInput.slice(0, 120) + "\x1B[29m")} ${c2.dim("\u2190 already sent, re-type below to override")}
50130
+ `));
50131
+ rl.line = lastSteeringInput;
50132
+ rl.cursor = lastSteeringInput.length;
50133
+ if (statusBar.isActive)
50134
+ statusBar.renderFooterPreserveCursor?.();
50135
+ } else {
50136
+ writeContent(() => process.stdout.write(` ${c2.yellow("\u23F8")} ${c2.dim("Paused. /resume to continue, /stop to kill.")}
50137
+ `));
50138
+ }
50139
+ showPrompt();
50140
+ }
50141
+ });
50142
+ }
50002
50143
  }
50003
50144
  async function runWithTUI(task, config, repoPath) {
50004
50145
  const repoRoot = resolve28(repoPath ?? cwd());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.83",
3
+ "version": "0.103.86",
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",