@oxecli/oxe 1.0.98 → 1.0.100

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, renderError, 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,10 +248,10 @@ 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
- process.stdout.write(`\x1b[31m${text}\x1b[0m\n`);
254
+ renderError(text);
255
255
  }
256
256
  else {
257
257
  process.stdout.write(mutedMarkdown(text) + "\n");
@@ -429,7 +429,7 @@ export class CLI {
429
429
  input = this.autoSubmit.input;
430
430
  spans = this.autoSubmit.spans;
431
431
  this.autoSubmit = null;
432
- dockRenderPrompt("", [], "You", "", hints);
432
+ dockRenderPrompt("", [], "", "You", hints);
433
433
  }
434
434
  else {
435
435
  const got = await askBottomPrompt("You", "❯", this.promptHistory, hints, this.lastPrompt);
@@ -494,7 +494,7 @@ export class CLI {
494
494
  // next one; Enter submits it as a new prompt.
495
495
  this.lastPrompt = "";
496
496
  queryStarted = true;
497
- const stopCapture = startDraftCapture(() => engine.interrupt(), (buf, sp) => dockRenderPrompt(buf, sp, "You", "", hints));
497
+ const stopCapture = startDraftCapture(() => engine.interrupt(), (buf, sp) => dockRenderPrompt(buf, sp, "", "You", hints));
498
498
  try {
499
499
  await engine.executeQuery(input, this.inputItems, this.story, spans);
500
500
  }
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, renderWarning, renderError, 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);
@@ -658,7 +674,7 @@ export class InferenceEngine {
658
674
  }
659
675
  function renderErrorPanel(msg, story) {
660
676
  const text = msg.replace(/\[bold yellow\]|\[yellow\]|\[\/.*?\]/g, "");
661
- renderPanel(text, "Error", "", true, "31");
677
+ renderError(text);
662
678
  story.push({ type: "error", text: `Error: ${text}` });
663
679
  }
664
680
  export { renderErrorPanel };
package/dist/ui.js CHANGED
@@ -479,6 +479,29 @@ 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
+ /**
483
+ * Render a warning as a framed panel (consistent shape whether shown live or
484
+ * replayed from a resumed session). The first sentence is emphasized; the rest
485
+ * is plain. No icon — just the panel frame and text.
486
+ */
487
+ export function renderWarning(message) {
488
+ const text = String(message ?? "").replace(/^[\s\u26A0\uFE0E]+/, "").trim();
489
+ const idx = text.indexOf(". ");
490
+ const head = idx === -1 ? text : text.slice(0, idx + 1);
491
+ const body = idx === -1 ? "" : text.slice(idx + 2).trim();
492
+ let content = `[bold yellow]${head}[/bold yellow]`;
493
+ if (body)
494
+ content += `\n[yellow]${body}[/yellow]`;
495
+ renderPanel(content, "Warning", "", false, "33");
496
+ }
497
+ /**
498
+ * Render an error as a framed panel (consistent shape whether shown live or
499
+ * replayed from a resumed session), matching renderWarning's framing.
500
+ */
501
+ export function renderError(message) {
502
+ const text = String(message ?? "").replace(/^Error:\s*/i, "").trim();
503
+ renderPanel(`[red]${text}[/red]`, "Error", "", false, "31");
504
+ }
482
505
  // ---------------------------------------------------------------------------
483
506
  // Table Panel Rendering
484
507
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.98",
3
+ "version": "1.0.100",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },