@oxecli/oxe 1.0.98 → 1.0.99

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/cli.js CHANGED
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { loadOrPrompt, default_reasoning_effort, max_action_chars, max_resume_history_items, runtimeOsSummary, max_context_tokens, context_overhead_margin, } from "./config.js";
5
5
  import { InferenceEngine, estimateTokens } from "./engine.js";
6
6
  import { saveSession, loadSession, listSessions, nextSessionId, conversationLabel, COMMAND_HELP, stripOrphanCalls, toolOutputFailed, } from "./sessions.js";
7
- import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, toolCallLabel, formatToolResult, truncateEllipsis, stripAnsi, plainLen, truncateStyled, terminalWidth, TOOL_RESULT_MAX_LINES, MAX_COMMAND_DISPLAY_CHARS, renderPanel, renderTableString, enableRawStdin, disableRawStdin, waitRawKey, dockAppendContent, dockSetInactive, dockTearDown, dockRenderPrompt, startDraftCapture, } from "./ui.js";
7
+ import { clearScreen, enableAnsi, askBottomPrompt, userDisplayText, aiMarkdown, mutedMarkdown, messageText, toolCallLabel, formatToolResult, truncateEllipsis, stripAnsi, plainLen, truncateStyled, terminalWidth, TOOL_RESULT_MAX_LINES, MAX_COMMAND_DISPLAY_CHARS, renderPanel, renderTableString, renderWarning, enableRawStdin, disableRawStdin, waitRawKey, dockAppendContent, dockSetInactive, dockTearDown, dockRenderPrompt, startDraftCapture, } from "./ui.js";
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const require = createRequire(import.meta.url);
10
10
  const packageInfo = require("../package.json");
@@ -248,7 +248,7 @@ export class CLI {
248
248
  process.stdout.write(mutedMarkdown(text) + "\n");
249
249
  }
250
250
  else if (typ === "warning") {
251
- process.stdout.write(`\x1b[33m${text}\x1b[0m\n`);
251
+ renderWarning(text);
252
252
  }
253
253
  else if (typ === "error") {
254
254
  process.stdout.write(`\x1b[31m${text}\x1b[0m\n`);
package/dist/config.js CHANGED
@@ -28,6 +28,9 @@ export const context_overhead_margin = 32_000;
28
28
  export const compact_keep_recent_turns = 4;
29
29
  export const max_summary_source_chars = 200_000;
30
30
  export const max_output_tokens = 16384;
31
+ /** Cap on how many times a single turn may be continued past the output-token
32
+ * cap before we give up extending it (prevents runaway generation). */
33
+ export const max_output_continuations = 6;
31
34
  export const max_empty_retries = 2;
32
35
  export const max_diff_source_chars = 1_000_000;
33
36
  export const max_grep_file_bytes = 20 * 1024 * 1024;
package/dist/engine.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import OpenAI from "openai";
2
- import { max_output_tokens, max_empty_retries, max_agent_steps, max_context_tokens, context_overhead_margin, compact_keep_recent_turns, max_summary_source_chars, max_read_file_stored_chars, } from "./config.js";
2
+ import { max_output_tokens, max_empty_retries, max_output_continuations, max_agent_steps, max_context_tokens, context_overhead_margin, compact_keep_recent_turns, max_summary_source_chars, max_read_file_stored_chars, } from "./config.js";
3
3
  import { getSystemPrompt } from "./system.js";
4
4
  import { buildTools, truncateToolOutput, toolReadFile, toolWriteFile, toolEditFile, toolBash, toolGlob, toolGrep, toolLoadSkill, } from "./tools.js";
5
- import { mutedMarkdown, aiMarkdown, tickDuration, safeCommitPoint, printAiChunk, toolCallLabel, formatToolResult, renderPanel, Spinner, hideCursor, dockAppendContent, dockReplaceTransient, dockTransientStart, } from "./ui.js";
5
+ import { mutedMarkdown, aiMarkdown, tickDuration, safeCommitPoint, printAiChunk, toolCallLabel, formatToolResult, renderPanel, renderWarning, Spinner, hideCursor, dockAppendContent, dockReplaceTransient, dockTransientStart, } from "./ui.js";
6
6
  import { reportUsage } from "./api.js";
7
7
  import { stripOrphanCalls, persistCompactionSummary, toolOutputFailed, } from "./sessions.js";
8
8
  import { takePendingDiffOutput } from "./tools.js";
@@ -315,10 +315,13 @@ export class InferenceEngine {
315
315
  const text = response?.output_text ?? content;
316
316
  if (text.trim())
317
317
  story.push({ type: "assistant", text });
318
- let incompleteReason = null;
318
+ // The incomplete reason must always be captured — not only in the empty
319
+ // case — so a response truncated by the output-token cap is never mistaken
320
+ // for a complete answer. `emptyMessage` is only relevant when nothing at
321
+ // all (text or tool call) was produced.
322
+ const incompleteReason = this.incompleteReason(response);
319
323
  let emptyMessage = null;
320
324
  if (!text.trim() && calls.length === 0) {
321
- incompleteReason = this.incompleteReason(response);
322
325
  emptyMessage = this.diagnoseEmptyResponse(response, sawReasoning);
323
326
  }
324
327
  return {
@@ -428,6 +431,7 @@ export class InferenceEngine {
428
431
  let conversation = [...inputItems];
429
432
  [conversation] = await this.compactIfNeeded(conversation, inputItems, story);
430
433
  let emptyRetries = 0;
434
+ let outputContinuations = 0;
431
435
  const retryPrompts = [];
432
436
  const stats = {
433
437
  thinking: 0,
@@ -524,6 +528,20 @@ export class InferenceEngine {
524
528
  prevId = null;
525
529
  pending = conversation;
526
530
  }
531
+ // A response cut off by the output-token cap is NOT a final answer —
532
+ // treating it as one is what returned force-truncated replies. Instead,
533
+ // continue generating from the previous response via `previous_response_id`
534
+ // until the response completes naturally. Bounded to prevent runaway
535
+ // output. Each continuation's output items are already committed above,
536
+ // so the full (concatenated) response is preserved in the transcript.
537
+ if (!compactedNow &&
538
+ incompleteReason === "max_output_tokens" &&
539
+ outputContinuations < max_output_continuations) {
540
+ outputContinuations++;
541
+ prevId = respId;
542
+ pending = [];
543
+ continue;
544
+ }
527
545
  if (emptyMessage !== null) {
528
546
  if (incompleteReason === "max_output_tokens" &&
529
547
  emptyRetries < max_empty_retries) {
@@ -617,12 +635,10 @@ export class InferenceEngine {
617
635
  // The preceding tool action already leaves the cursor on a blank row, so
618
636
  // no extra newline is needed above; renderPanel ends with its own "\n",
619
637
  // and the prompt adds one leading "\n" -> exactly one blank row below.
620
- const maxIterationMsg = "Max tool-call iterations reached for this turn. The work so far is " +
638
+ const maxIterationMsg = "Max tool-call iterations reached for this turn. The work so far is " +
621
639
  "saved. If you want the agent to keep going, type continue and the next " +
622
640
  "step will resume from where it left off.";
623
- renderPanel("[bold yellow]⚠ Max tool-call iterations reached for this turn.[/bold yellow]\n" +
624
- "[yellow]The work so far is saved. If you want the agent to keep going, type " +
625
- "[bold]continue[/bold] and the next step will resume from where it left off.[/yellow]", "Warning", "", false, "33");
641
+ renderWarning(maxIterationMsg);
626
642
  story.push({ type: "warning", text: maxIterationMsg });
627
643
  if (retryPrompts.length)
628
644
  dropRetryPrompts(conversation, inputItems, retryPrompts);
package/dist/ui.js CHANGED
@@ -479,6 +479,25 @@ function panelString(content, title = "", subtitle = "", expand = true, borderSt
479
479
  export function renderPanel(content, title = "", subtitle = "", expand = true, borderStyle = "90", titleAlign = "center") {
480
480
  dockAppendContent(panelString(content, title, subtitle, expand, borderStyle, titleAlign) + "\n");
481
481
  }
482
+ /** Warning sign with the text-presentation variation selector, so it renders
483
+ * as a monochrome icon (matching the app's ✓/✗ symbols) rather than a color
484
+ * emoji that varies by platform. */
485
+ export const WARNING_ICON = "\u26A0\uFE0E";
486
+ /**
487
+ * Render a warning as a framed panel (consistent shape whether shown live or
488
+ * replayed from a resumed session), using the text-presentation warning icon
489
+ * instead of an emoji. The first sentence is emphasized; the rest is plain.
490
+ */
491
+ export function renderWarning(message) {
492
+ const text = String(message ?? "").replace(/^[\s\u26A0\uFE0E]+/, "").trim();
493
+ const idx = text.indexOf(". ");
494
+ const head = idx === -1 ? text : text.slice(0, idx + 1);
495
+ const body = idx === -1 ? "" : text.slice(idx + 2).trim();
496
+ let content = `[bold yellow]${WARNING_ICON} ${head}[/bold yellow]`;
497
+ if (body)
498
+ content += `\n[yellow]${body}[/yellow]`;
499
+ renderPanel(content, "Warning", "", false, "33");
500
+ }
482
501
  // ---------------------------------------------------------------------------
483
502
  // Table Panel Rendering
484
503
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.98",
3
+ "version": "1.0.99",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },