@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 +2 -2
- package/dist/config.js +3 -0
- package/dist/engine.js +24 -8
- package/dist/ui.js +19 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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 = "
|
|
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
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|