fix: /bug report command, thinking indicator (#381)
- Fix `/bug` report command - Fix thinking indicator
This commit is contained in:
@@ -1,20 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
# resolve script path in case of symlink
|
|
||||||
SOURCE="$0"
|
|
||||||
while [ -h "$SOURCE" ]; do
|
|
||||||
DIR=$(dirname "$SOURCE")
|
|
||||||
SOURCE=$(readlink "$SOURCE")
|
|
||||||
case "$SOURCE" in
|
|
||||||
/*) ;; # absolute path
|
|
||||||
*) SOURCE="$DIR/$SOURCE" ;; # relative path
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
DIR=$(cd "$(dirname "$SOURCE")" && pwd)
|
|
||||||
if command -v node >/dev/null 2>&1; then
|
|
||||||
exec node "$DIR/../dist/cli.js" "$@"
|
|
||||||
elif command -v bun >/dev/null 2>&1; then
|
|
||||||
exec bun "$DIR/../dist/cli.js" "$@"
|
|
||||||
else
|
|
||||||
echo "Error: node or bun is required to run codex" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
3
codex-cli/bin/codex.js
Normal file → Executable file
3
codex-cli/bin/codex.js
Normal file → Executable file
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
// Unified entry point for Codex CLI on all platforms
|
// Unified entry point for Codex CLI on all platforms
|
||||||
// Dynamically loads the compiled ESM bundle in dist/cli.js
|
// Dynamically loads the compiled ESM bundle in dist/cli.js
|
||||||
|
|
||||||
@@ -18,7 +19,9 @@ const cliUrl = pathToFileURL(cliPath).href;
|
|||||||
try {
|
try {
|
||||||
await import(cliUrl);
|
await import(cliUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
"marked-terminal": "^7.3.0",
|
"marked-terminal": "^7.3.0",
|
||||||
"meow": "^13.2.0",
|
"meow": "^13.2.0",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"openai": "^4.89.0",
|
"openai": "^4.95.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"shell-quote": "^1.8.2",
|
"shell-quote": "^1.8.2",
|
||||||
"strip-ansi": "^7.1.0",
|
"strip-ansi": "^7.1.0",
|
||||||
|
|||||||
@@ -1,82 +1,28 @@
|
|||||||
import { log, isLoggingEnabled } from "../../utils/agent/log.js";
|
import { log, isLoggingEnabled } from "../../utils/agent/log.js";
|
||||||
import Spinner from "../vendor/ink-spinner.js";
|
|
||||||
import { Box, Text, useInput, useStdin } from "ink";
|
import { Box, Text, useInput, useStdin } from "ink";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useInterval } from "use-interval";
|
import { useInterval } from "use-interval";
|
||||||
|
|
||||||
const thinkingTexts = ["Thinking"]; /* [
|
// Retaining a single static placeholder text for potential future use. The
|
||||||
"Consulting the rubber duck",
|
// more elaborate randomised thinking prompts were removed to streamline the
|
||||||
"Maximizing paperclips",
|
// UI – the elapsed‑time counter now provides sufficient feedback.
|
||||||
"Reticulating splines",
|
|
||||||
"Immanentizing the Eschaton",
|
|
||||||
"Thinking",
|
|
||||||
"Thinking about thinking",
|
|
||||||
"Spinning in circles",
|
|
||||||
"Counting dust specks",
|
|
||||||
"Updating priors",
|
|
||||||
"Feeding the utility monster",
|
|
||||||
"Taking off",
|
|
||||||
"Wireheading",
|
|
||||||
"Counting to infinity",
|
|
||||||
"Staring into the Basilisk",
|
|
||||||
"Negotiationing acausal trades",
|
|
||||||
"Searching the library of babel",
|
|
||||||
"Multiplying matrices",
|
|
||||||
"Solving the halting problem",
|
|
||||||
"Counting grains of sand",
|
|
||||||
"Simulating a simulation",
|
|
||||||
"Asking the oracle",
|
|
||||||
"Detangling qubits",
|
|
||||||
"Reading tea leaves",
|
|
||||||
"Pondering universal love and transcendent joy",
|
|
||||||
"Feeling the AGI",
|
|
||||||
"Shaving the yak",
|
|
||||||
"Escaping local minima",
|
|
||||||
"Pruning the search tree",
|
|
||||||
"Descending the gradient",
|
|
||||||
"Bikeshedding",
|
|
||||||
"Securing funding",
|
|
||||||
"Rewriting in Rust",
|
|
||||||
"Engaging infinite improbability drive",
|
|
||||||
"Clapping with one hand",
|
|
||||||
"Synthesizing",
|
|
||||||
"Rebasing thesis onto antithesis",
|
|
||||||
"Transcending the loop",
|
|
||||||
"Frogeposting",
|
|
||||||
"Summoning",
|
|
||||||
"Peeking beyond the veil",
|
|
||||||
"Seeking",
|
|
||||||
"Entering deep thought",
|
|
||||||
"Meditating",
|
|
||||||
"Decomposing",
|
|
||||||
"Creating",
|
|
||||||
"Beseeching the machine spirit",
|
|
||||||
"Calibrating moral compass",
|
|
||||||
"Collapsing the wave function",
|
|
||||||
"Doodling",
|
|
||||||
"Translating whale song",
|
|
||||||
"Whispering to silicon",
|
|
||||||
"Looking for semicolons",
|
|
||||||
"Asking ChatGPT",
|
|
||||||
"Bargaining with entropy",
|
|
||||||
"Channeling",
|
|
||||||
"Cooking",
|
|
||||||
"Parroting stochastically",
|
|
||||||
]; */
|
|
||||||
|
|
||||||
export default function TerminalChatInputThinking({
|
export default function TerminalChatInputThinking({
|
||||||
onInterrupt,
|
onInterrupt,
|
||||||
active,
|
active,
|
||||||
|
thinkingSeconds,
|
||||||
}: {
|
}: {
|
||||||
onInterrupt: () => void;
|
onInterrupt: () => void;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
thinkingSeconds: number;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const [dots, setDots] = useState("");
|
|
||||||
const [awaitingConfirm, setAwaitingConfirm] = useState(false);
|
const [awaitingConfirm, setAwaitingConfirm] = useState(false);
|
||||||
|
const [dots, setDots] = useState("");
|
||||||
|
|
||||||
const [thinkingText, setThinkingText] = useState(
|
// Animate the ellipsis
|
||||||
() => thinkingTexts[Math.floor(Math.random() * thinkingTexts.length)],
|
useInterval(() => {
|
||||||
);
|
setDots((prev) => (prev.length < 3 ? prev + "." : ""));
|
||||||
|
}, 500);
|
||||||
|
|
||||||
const { stdin, setRawMode } = useStdin();
|
const { stdin, setRawMode } = useStdin();
|
||||||
|
|
||||||
@@ -110,25 +56,7 @@ export default function TerminalChatInputThinking({
|
|||||||
};
|
};
|
||||||
}, [stdin, awaitingConfirm, onInterrupt, active, setRawMode]);
|
}, [stdin, awaitingConfirm, onInterrupt, active, setRawMode]);
|
||||||
|
|
||||||
useInterval(() => {
|
// No timers required beyond tracking the elapsed seconds supplied via props.
|
||||||
setDots((prev) => (prev.length < 3 ? prev + "." : ""));
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
useInterval(
|
|
||||||
() => {
|
|
||||||
setThinkingText((prev) => {
|
|
||||||
let next = prev;
|
|
||||||
if (thinkingTexts.length > 1) {
|
|
||||||
while (next === prev) {
|
|
||||||
next =
|
|
||||||
thinkingTexts[Math.floor(Math.random() * thinkingTexts.length)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
active ? 30000 : null,
|
|
||||||
);
|
|
||||||
|
|
||||||
useInput(
|
useInput(
|
||||||
(_input, key) => {
|
(_input, key) => {
|
||||||
@@ -153,12 +81,41 @@ export default function TerminalChatInputThinking({
|
|||||||
{ isActive: active },
|
{ isActive: active },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Custom ball animation including the elapsed seconds
|
||||||
|
const ballFrames = [
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ●)",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"(● )",
|
||||||
|
];
|
||||||
|
|
||||||
|
const [frame, setFrame] = useState(0);
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
setFrame((idx) => (idx + 1) % ballFrames.length);
|
||||||
|
}, 80);
|
||||||
|
|
||||||
|
// Preserve the spinner (ball) animation while keeping the elapsed seconds
|
||||||
|
// text static. We achieve this by rendering the bouncing ball inside the
|
||||||
|
// parentheses and appending the seconds counter *after* the spinner rather
|
||||||
|
// than injecting it directly next to the ball (which caused the counter to
|
||||||
|
// move horizontally together with the ball).
|
||||||
|
|
||||||
|
const frameTemplate = ballFrames[frame] ?? ballFrames[0];
|
||||||
|
const frameWithSeconds = `${frameTemplate} ${thinkingSeconds}s`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" gap={1}>
|
<Box flexDirection="column" gap={1}>
|
||||||
<Box gap={2}>
|
<Box gap={2}>
|
||||||
<Spinner type="ball" />
|
<Text>{frameWithSeconds}</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{thinkingText}
|
Thinking
|
||||||
{dots}
|
{dots}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
addToHistory,
|
addToHistory,
|
||||||
} from "../../utils/storage/command-history.js";
|
} from "../../utils/storage/command-history.js";
|
||||||
import { clearTerminal, onExit } from "../../utils/terminal.js";
|
import { clearTerminal, onExit } from "../../utils/terminal.js";
|
||||||
import Spinner from "../vendor/ink-spinner.js";
|
|
||||||
import TextInput from "../vendor/ink-text-input.js";
|
import TextInput from "../vendor/ink-text-input.js";
|
||||||
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
@@ -45,6 +44,7 @@ export default function TerminalChatInput({
|
|||||||
onCompact,
|
onCompact,
|
||||||
interruptAgent,
|
interruptAgent,
|
||||||
active,
|
active,
|
||||||
|
thinkingSeconds,
|
||||||
items = [],
|
items = [],
|
||||||
}: {
|
}: {
|
||||||
isNew: boolean;
|
isNew: boolean;
|
||||||
@@ -66,6 +66,7 @@ export default function TerminalChatInput({
|
|||||||
onCompact: () => void;
|
onCompact: () => void;
|
||||||
interruptAgent: () => void;
|
interruptAgent: () => void;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
thinkingSeconds: number;
|
||||||
// New: current conversation items so we can include them in bug reports
|
// New: current conversation items so we can include them in bug reports
|
||||||
items?: Array<ResponseItem>;
|
items?: Array<ResponseItem>;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
@@ -265,7 +266,9 @@ export default function TerminalChatInput({
|
|||||||
items: items ?? [],
|
items: items ?? [],
|
||||||
cliVersion: CLI_VERSION,
|
cliVersion: CLI_VERSION,
|
||||||
model: loadConfig().model ?? "unknown",
|
model: loadConfig().model ?? "unknown",
|
||||||
platform: `${os.platform()} ${os.arch()} ${os.release()}`,
|
platform: [os.platform(), os.arch(), os.release()]
|
||||||
|
.map((s) => `\`${s}\``)
|
||||||
|
.join(" | "),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open the URL in the user's default browser
|
// Open the URL in the user's default browser
|
||||||
@@ -416,6 +419,7 @@ export default function TerminalChatInput({
|
|||||||
<TerminalChatInputThinking
|
<TerminalChatInputThinking
|
||||||
onInterrupt={interruptAgent}
|
onInterrupt={interruptAgent}
|
||||||
active={active}
|
active={active}
|
||||||
|
thinkingSeconds={thinkingSeconds}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box paddingX={1}>
|
<Box paddingX={1}>
|
||||||
@@ -491,12 +495,42 @@ export default function TerminalChatInput({
|
|||||||
function TerminalChatInputThinking({
|
function TerminalChatInputThinking({
|
||||||
onInterrupt,
|
onInterrupt,
|
||||||
active,
|
active,
|
||||||
|
thinkingSeconds,
|
||||||
}: {
|
}: {
|
||||||
onInterrupt: () => void;
|
onInterrupt: () => void;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
thinkingSeconds: number;
|
||||||
}) {
|
}) {
|
||||||
const [dots, setDots] = useState("");
|
|
||||||
const [awaitingConfirm, setAwaitingConfirm] = useState(false);
|
const [awaitingConfirm, setAwaitingConfirm] = useState(false);
|
||||||
|
const [dots, setDots] = useState("");
|
||||||
|
|
||||||
|
// Animate ellipsis
|
||||||
|
useInterval(() => {
|
||||||
|
setDots((prev) => (prev.length < 3 ? prev + "." : ""));
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Spinner frames with embedded seconds
|
||||||
|
const ballFrames = [
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ●)",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"(● )",
|
||||||
|
];
|
||||||
|
const [frame, setFrame] = useState(0);
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
setFrame((idx) => (idx + 1) % ballFrames.length);
|
||||||
|
}, 80);
|
||||||
|
|
||||||
|
// Keep the elapsed‑seconds text fixed while the ball animation moves.
|
||||||
|
const frameTemplate = ballFrames[frame] ?? ballFrames[0];
|
||||||
|
const frameWithSeconds = `${frameTemplate} ${thinkingSeconds}s`;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
// Raw stdin listener to catch the case where the terminal delivers two
|
// Raw stdin listener to catch the case where the terminal delivers two
|
||||||
@@ -544,10 +578,7 @@ function TerminalChatInputThinking({
|
|||||||
};
|
};
|
||||||
}, [stdin, awaitingConfirm, onInterrupt, active, setRawMode]);
|
}, [stdin, awaitingConfirm, onInterrupt, active, setRawMode]);
|
||||||
|
|
||||||
// Cycle the "Thinking…" animation dots.
|
// No local timer: the parent component supplies the elapsed time via props.
|
||||||
useInterval(() => {
|
|
||||||
setDots((prev) => (prev.length < 3 ? prev + "." : ""));
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// Listen for the escape key to allow the user to interrupt the current
|
// Listen for the escape key to allow the user to interrupt the current
|
||||||
// operation. We require two presses within a short window (1.5s) to avoid
|
// operation. We require two presses within a short window (1.5s) to avoid
|
||||||
@@ -578,8 +609,11 @@ function TerminalChatInputThinking({
|
|||||||
return (
|
return (
|
||||||
<Box flexDirection="column" gap={1}>
|
<Box flexDirection="column" gap={1}>
|
||||||
<Box gap={2}>
|
<Box gap={2}>
|
||||||
<Spinner type="ball" />
|
<Text>{frameWithSeconds}</Text>
|
||||||
<Text>Thinking{dots}</Text>
|
<Text>
|
||||||
|
Thinking
|
||||||
|
{dots}
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{awaitingConfirm && (
|
{awaitingConfirm && (
|
||||||
<Text dimColor>
|
<Text dimColor>
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
addToHistory,
|
addToHistory,
|
||||||
} from "../../utils/storage/command-history.js";
|
} from "../../utils/storage/command-history.js";
|
||||||
import { clearTerminal, onExit } from "../../utils/terminal.js";
|
import { clearTerminal, onExit } from "../../utils/terminal.js";
|
||||||
import Spinner from "../vendor/ink-spinner.js";
|
|
||||||
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import React, { useCallback, useState, Fragment, useEffect } from "react";
|
import React, { useCallback, useState, Fragment, useEffect } from "react";
|
||||||
@@ -37,39 +36,7 @@ const typeHelpText = `ctrl+c to exit | "/clear" to reset context | "/help" for c
|
|||||||
const DEBUG_HIST =
|
const DEBUG_HIST =
|
||||||
process.env["DEBUG_TCI"] === "1" || process.env["DEBUG_TCI"] === "true";
|
process.env["DEBUG_TCI"] === "1" || process.env["DEBUG_TCI"] === "true";
|
||||||
|
|
||||||
const thinkingTexts = ["Thinking"]; /* [
|
// Placeholder for potential dynamic prompts – currently not used.
|
||||||
"Consulting the rubber duck",
|
|
||||||
"Maximizing paperclips",
|
|
||||||
"Reticulating splines",
|
|
||||||
"Immanentizing the Eschaton",
|
|
||||||
"Thinking",
|
|
||||||
"Thinking about thinking",
|
|
||||||
"Spinning in circles",
|
|
||||||
"Counting dust specks",
|
|
||||||
"Updating priors",
|
|
||||||
"Feeding the utility monster",
|
|
||||||
"Taking off",
|
|
||||||
"Wireheading",
|
|
||||||
"Counting to infinity",
|
|
||||||
"Staring into the Basilisk",
|
|
||||||
"Running acausal tariff negotiations",
|
|
||||||
"Searching the library of babel",
|
|
||||||
"Multiplying matrices",
|
|
||||||
"Solving the halting problem",
|
|
||||||
"Counting grains of sand",
|
|
||||||
"Simulating a simulation",
|
|
||||||
"Asking the oracle",
|
|
||||||
"Detangling qubits",
|
|
||||||
"Reading tea leaves",
|
|
||||||
"Pondering universal love and transcendent joy",
|
|
||||||
"Feeling the AGI",
|
|
||||||
"Shaving the yak",
|
|
||||||
"Escaping local minima",
|
|
||||||
"Pruning the search tree",
|
|
||||||
"Descending the gradient",
|
|
||||||
"Painting the bikeshed",
|
|
||||||
"Securing funding",
|
|
||||||
]; */
|
|
||||||
|
|
||||||
export default function TerminalChatInput({
|
export default function TerminalChatInput({
|
||||||
isNew: _isNew,
|
isNew: _isNew,
|
||||||
@@ -87,6 +54,7 @@ export default function TerminalChatInput({
|
|||||||
openHelpOverlay,
|
openHelpOverlay,
|
||||||
interruptAgent,
|
interruptAgent,
|
||||||
active,
|
active,
|
||||||
|
thinkingSeconds,
|
||||||
}: {
|
}: {
|
||||||
isNew: boolean;
|
isNew: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -106,6 +74,7 @@ export default function TerminalChatInput({
|
|||||||
openHelpOverlay: () => void;
|
openHelpOverlay: () => void;
|
||||||
interruptAgent: () => void;
|
interruptAgent: () => void;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
thinkingSeconds: number;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
const [selectedSuggestion, setSelectedSuggestion] = useState<number>(0);
|
const [selectedSuggestion, setSelectedSuggestion] = useState<number>(0);
|
||||||
@@ -389,6 +358,7 @@ export default function TerminalChatInput({
|
|||||||
<TerminalChatInputThinking
|
<TerminalChatInputThinking
|
||||||
onInterrupt={interruptAgent}
|
onInterrupt={interruptAgent}
|
||||||
active={active}
|
active={active}
|
||||||
|
thinkingSeconds={thinkingSeconds}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -454,15 +424,43 @@ export default function TerminalChatInput({
|
|||||||
function TerminalChatInputThinking({
|
function TerminalChatInputThinking({
|
||||||
onInterrupt,
|
onInterrupt,
|
||||||
active,
|
active,
|
||||||
|
thinkingSeconds,
|
||||||
}: {
|
}: {
|
||||||
onInterrupt: () => void;
|
onInterrupt: () => void;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
thinkingSeconds: number;
|
||||||
}) {
|
}) {
|
||||||
const [dots, setDots] = useState("");
|
|
||||||
const [awaitingConfirm, setAwaitingConfirm] = useState(false);
|
const [awaitingConfirm, setAwaitingConfirm] = useState(false);
|
||||||
|
const [dots, setDots] = useState("");
|
||||||
|
|
||||||
const [thinkingText] = useState(
|
// Animate ellipsis
|
||||||
() => thinkingTexts[Math.floor(Math.random() * thinkingTexts.length)],
|
useInterval(() => {
|
||||||
|
setDots((prev) => (prev.length < 3 ? prev + "." : ""));
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Spinner frames with seconds embedded
|
||||||
|
const ballFrames = [
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ●)",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"(● )",
|
||||||
|
];
|
||||||
|
const [frame, setFrame] = useState(0);
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
setFrame((idx) => (idx + 1) % ballFrames.length);
|
||||||
|
}, 80);
|
||||||
|
|
||||||
|
const frameTemplate = ballFrames[frame] ?? ballFrames[0];
|
||||||
|
const frameWithSeconds = (frameTemplate as string).replace(
|
||||||
|
"●",
|
||||||
|
`●${thinkingSeconds}s`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
@@ -511,9 +509,7 @@ function TerminalChatInputThinking({
|
|||||||
};
|
};
|
||||||
}, [stdin, awaitingConfirm, onInterrupt, active, setRawMode]);
|
}, [stdin, awaitingConfirm, onInterrupt, active, setRawMode]);
|
||||||
|
|
||||||
useInterval(() => {
|
// Elapsed time provided via props – no local interval needed.
|
||||||
setDots((prev) => (prev.length < 3 ? prev + "." : ""));
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
useInput(
|
useInput(
|
||||||
(_input, key) => {
|
(_input, key) => {
|
||||||
@@ -541,9 +537,9 @@ function TerminalChatInputThinking({
|
|||||||
return (
|
return (
|
||||||
<Box flexDirection="column" gap={1}>
|
<Box flexDirection="column" gap={1}>
|
||||||
<Box gap={2}>
|
<Box gap={2}>
|
||||||
<Spinner type="ball" />
|
<Text>{frameWithSeconds}</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{thinkingText}
|
Thinking
|
||||||
{dots}
|
{dots}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ export default function TerminalChat({
|
|||||||
return {};
|
return {};
|
||||||
}}
|
}}
|
||||||
items={items}
|
items={items}
|
||||||
|
thinkingSeconds={thinkingSeconds}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{overlayMode === "history" && (
|
{overlayMode === "history" && (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { ResponseItem } from "openai/resources/responses/responses.mjs";
|
|||||||
|
|
||||||
import TerminalChatResponseItem from "./terminal-chat-response-item.js";
|
import TerminalChatResponseItem from "./terminal-chat-response-item.js";
|
||||||
import TerminalHeader from "./terminal-header.js";
|
import TerminalHeader from "./terminal-header.js";
|
||||||
import { Box, Static, Text } from "ink";
|
import { Box, Static } from "ink";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
|
|
||||||
// A batch entry can either be a standalone response item or a grouped set of
|
// A batch entry can either be a standalone response item or a grouped set of
|
||||||
@@ -26,8 +26,9 @@ type MessageHistoryProps = {
|
|||||||
const MessageHistory: React.FC<MessageHistoryProps> = ({
|
const MessageHistory: React.FC<MessageHistoryProps> = ({
|
||||||
batch,
|
batch,
|
||||||
headerProps,
|
headerProps,
|
||||||
loading,
|
// `loading` and `thinkingSeconds` handled by input component now.
|
||||||
thinkingSeconds,
|
loading: _loading,
|
||||||
|
thinkingSeconds: _thinkingSeconds,
|
||||||
fullStdout,
|
fullStdout,
|
||||||
}) => {
|
}) => {
|
||||||
// Flatten batch entries to response items.
|
// Flatten batch entries to response items.
|
||||||
@@ -35,11 +36,8 @@ const MessageHistory: React.FC<MessageHistoryProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
{loading && (
|
{/* The dedicated thinking indicator in the input area now displays the
|
||||||
<Box marginTop={1}>
|
elapsed time, so we no longer render a separate counter here. */}
|
||||||
<Text color="yellow">{`thinking for ${thinkingSeconds}s`}</Text>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Static items={["header", ...messages]}>
|
<Static items={["header", ...messages]}>
|
||||||
{(item, index) => {
|
{(item, index) => {
|
||||||
if (item === "header") {
|
if (item === "header") {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { ResponseItem } from "openai/resources/responses/responses.mjs";
|
import type {
|
||||||
|
ResponseItem,
|
||||||
|
ResponseOutputItem,
|
||||||
|
} from "openai/resources/responses/responses.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a GitHub issues‐new URL that pre‑fills the Codex 2‑bug‑report.yml
|
* Build a GitHub issues‐new URL that pre‑fills the Codex 2‑bug‑report.yml
|
||||||
@@ -12,7 +15,7 @@ export function buildBugReportUrl({
|
|||||||
platform,
|
platform,
|
||||||
}: {
|
}: {
|
||||||
/** Chat history so we can summarise user steps */
|
/** Chat history so we can summarise user steps */
|
||||||
items: Array<ResponseItem>;
|
items: Array<ResponseItem | ResponseOutputItem>;
|
||||||
/** CLI revision string (e.g. output of `codex --revision`) */
|
/** CLI revision string (e.g. output of `codex --revision`) */
|
||||||
cliVersion: string;
|
cliVersion: string;
|
||||||
/** Active model name */
|
/** Active model name */
|
||||||
@@ -25,16 +28,10 @@ export function buildBugReportUrl({
|
|||||||
labels: "bug",
|
labels: "bug",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Template ids -------------------------------------------------------------
|
|
||||||
params.set("version", cliVersion);
|
params.set("version", cliVersion);
|
||||||
params.set("model", model);
|
params.set("model", model);
|
||||||
|
params.set("platform", platform);
|
||||||
|
|
||||||
// The platform input has no explicit `id`, so GitHub falls back to a slug of
|
|
||||||
// the label text. For “What platform is your computer?” that slug is:
|
|
||||||
// what-platform-is-your-computer
|
|
||||||
params.set("what-platform-is-your-computer", platform);
|
|
||||||
|
|
||||||
// Build the steps bullet list ---------------------------------------------
|
|
||||||
const bullets: Array<string> = [];
|
const bullets: Array<string> = [];
|
||||||
for (let i = 0; i < items.length; ) {
|
for (let i = 0; i < items.length; ) {
|
||||||
const entry = items[i];
|
const entry = items[i];
|
||||||
@@ -50,12 +47,14 @@ export function buildBugReportUrl({
|
|||||||
let reasoning = 0;
|
let reasoning = 0;
|
||||||
let toolCalls = 0;
|
let toolCalls = 0;
|
||||||
let j = i + 1;
|
let j = i + 1;
|
||||||
while (
|
while (j < items.length) {
|
||||||
j < items.length &&
|
|
||||||
!(entry?.type === "message" && entry.role === "user")
|
|
||||||
) {
|
|
||||||
const it = items[j];
|
const it = items[j];
|
||||||
if (it?.type === "message" && it?.role === "assistant") {
|
if (it?.type === "message" && it?.role === "user") {
|
||||||
|
break;
|
||||||
|
} else if (
|
||||||
|
it?.type === "reasoning" ||
|
||||||
|
(it?.type === "message" && it?.role === "assistant")
|
||||||
|
) {
|
||||||
reasoning += 1;
|
reasoning += 1;
|
||||||
} else if (it?.type === "function_call") {
|
} else if (it?.type === "function_call") {
|
||||||
toolCalls += 1;
|
toolCalls += 1;
|
||||||
@@ -63,8 +62,10 @@ export function buildBugReportUrl({
|
|||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const codeBlock = `\`\`\`\n ${messageText}\n \`\`\``;
|
||||||
|
|
||||||
bullets.push(
|
bullets.push(
|
||||||
`- "${messageText}"\n - \`${reasoning} reasoning steps\` | \`${toolCalls} tool calls\``,
|
`- ${codeBlock}\n - \`${reasoning} reasoning\` | \`${toolCalls} tool\``,
|
||||||
);
|
);
|
||||||
|
|
||||||
i = j;
|
i = j;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ describe("TerminalChatInput compact command", () => {
|
|||||||
onCompact: () => {},
|
onCompact: () => {},
|
||||||
interruptAgent: () => {},
|
interruptAgent: () => {},
|
||||||
active: true,
|
active: true,
|
||||||
|
thinkingSeconds: 0,
|
||||||
};
|
};
|
||||||
const { lastFrameStripped } = renderTui(<TerminalChatInput {...props} />);
|
const { lastFrameStripped } = renderTui(<TerminalChatInput {...props} />);
|
||||||
const frame = lastFrameStripped();
|
const frame = lastFrameStripped();
|
||||||
|
|||||||
@@ -30,5 +30,5 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["src", "tests"]
|
"include": ["src", "tests", "bin"]
|
||||||
}
|
}
|
||||||
|
|||||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -65,7 +65,7 @@ importers:
|
|||||||
specifier: ^10.1.0
|
specifier: ^10.1.0
|
||||||
version: 10.1.1
|
version: 10.1.1
|
||||||
openai:
|
openai:
|
||||||
specifier: ^4.89.0
|
specifier: ^4.95.1
|
||||||
version: 4.95.1(ws@8.18.1)(zod@3.24.3)
|
version: 4.95.1(ws@8.18.1)(zod@3.24.3)
|
||||||
react:
|
react:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
@@ -2739,7 +2739,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/node-fetch@2.6.12':
|
'@types/node-fetch@2.6.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.19.86
|
'@types/node': 22.14.1
|
||||||
form-data: 4.0.2
|
form-data: 4.0.2
|
||||||
|
|
||||||
'@types/node@18.19.86':
|
'@types/node@18.19.86':
|
||||||
|
|||||||
Reference in New Issue
Block a user