mirror of
https://github.com/openai/codex.git
synced 2026-05-03 12:52:11 +03:00
merge
This commit is contained in:
@@ -5,7 +5,7 @@ import type { ColorName } from "chalk";
|
||||
import type { ResponseItem } from "openai/resources/responses/responses.mjs";
|
||||
|
||||
import TerminalChatInput from "./terminal-chat-input.js";
|
||||
import { TerminalChatToolCallCommand } from "./terminal-chat-tool-call-item.js";
|
||||
import { TerminalChatToolCallCommand } from "./terminal-chat-tool-call-command.js";
|
||||
import {
|
||||
calculateContextPercentRemaining,
|
||||
uniqueById,
|
||||
@@ -19,17 +19,20 @@ import { isLoggingEnabled, log } from "../../utils/agent/log.js";
|
||||
import { ReviewDecision } from "../../utils/agent/review.js";
|
||||
import { generateCompactSummary } from "../../utils/compact-summary.js";
|
||||
import { OPENAI_BASE_URL } from "../../utils/config.js";
|
||||
import { extractAppliedPatches as _extractAppliedPatches } from "../../utils/extract-applied-patches.js";
|
||||
import { getGitDiff } from "../../utils/get-diff.js";
|
||||
import { createInputItem } from "../../utils/input-utils.js";
|
||||
import { getAvailableModels } from "../../utils/model-utils.js";
|
||||
import { CLI_VERSION } from "../../utils/session.js";
|
||||
import { shortCwd } from "../../utils/short-path.js";
|
||||
import { saveRollout } from "../../utils/storage/save-rollout.js";
|
||||
import ApprovalModeOverlay from "../approval-mode-overlay.js";
|
||||
import DiffOverlay from "../diff-overlay.js";
|
||||
import HelpOverlay from "../help-overlay.js";
|
||||
import HistoryOverlay from "../history-overlay.js";
|
||||
import ModelOverlay from "../model-overlay.js";
|
||||
import { Box, Text } from "ink";
|
||||
import { exec } from "node:child_process";
|
||||
import { spawn } from "node:child_process";
|
||||
import OpenAI from "openai";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { inspect } from "util";
|
||||
@@ -59,6 +62,7 @@ const colorsByPolicy: Record<ApprovalPolicy, ColorName | undefined> = {
|
||||
async function generateCommandExplanation(
|
||||
command: Array<string>,
|
||||
model: string,
|
||||
flexMode: boolean,
|
||||
): Promise<string> {
|
||||
try {
|
||||
// Create a temporary OpenAI client
|
||||
@@ -73,6 +77,7 @@ async function generateCommandExplanation(
|
||||
// Create a prompt that asks for an explanation with a more detailed system prompt
|
||||
const response = await oai.chat.completions.create({
|
||||
model,
|
||||
...(flexMode ? { service_tier: "flex" } : {}),
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
@@ -142,7 +147,11 @@ export default function TerminalChat({
|
||||
const handleCompact = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const summary = await generateCompactSummary(items, model);
|
||||
const summary = await generateCompactSummary(
|
||||
items,
|
||||
model,
|
||||
Boolean(config.flexMode),
|
||||
);
|
||||
setItems([
|
||||
{
|
||||
id: `compact-${Date.now()}`,
|
||||
@@ -174,9 +183,16 @@ export default function TerminalChat({
|
||||
submitConfirmation,
|
||||
} = useConfirmation();
|
||||
const [overlayMode, setOverlayMode] = useState<
|
||||
"none" | "history" | "model" | "approval" | "help"
|
||||
"none" | "history" | "model" | "approval" | "help" | "diff"
|
||||
>("none");
|
||||
|
||||
// Store the diff text when opening the diff overlay so the view isn’t
|
||||
// recomputed on every re‑render while it is open.
|
||||
// diffText is passed down to the DiffOverlay component. The setter is
|
||||
// currently unused but retained for potential future updates. Prefix with
|
||||
// an underscore so eslint ignores the unused variable.
|
||||
const [diffText, _setDiffText] = useState<string>("");
|
||||
|
||||
const [initialPrompt, setInitialPrompt] = useState(_initialPrompt);
|
||||
const [initialImagePaths, setInitialImagePaths] =
|
||||
useState(_initialImagePaths);
|
||||
@@ -200,6 +216,13 @@ export default function TerminalChat({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Skip recreating the agent if awaiting a decision on a pending confirmation
|
||||
if (confirmationPrompt != null) {
|
||||
if (isLoggingEnabled()) {
|
||||
log("skip AgentLoop recreation due to pending confirmationPrompt");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isLoggingEnabled()) {
|
||||
log("creating NEW AgentLoop");
|
||||
log(
|
||||
@@ -245,7 +268,11 @@ export default function TerminalChat({
|
||||
log(`Generating explanation for command: ${commandForDisplay}`);
|
||||
|
||||
// Generate an explanation using the same model
|
||||
const explanation = await generateCommandExplanation(command, model);
|
||||
const explanation = await generateCommandExplanation(
|
||||
command,
|
||||
model,
|
||||
Boolean(config.flexMode),
|
||||
);
|
||||
log(`Generated explanation: ${explanation}`);
|
||||
|
||||
// Ask for confirmation again, but with the explanation
|
||||
@@ -283,13 +310,10 @@ export default function TerminalChat({
|
||||
agentRef.current = undefined;
|
||||
forceUpdate(); // re‑render after teardown too
|
||||
};
|
||||
}, [
|
||||
model,
|
||||
config,
|
||||
approvalPolicy,
|
||||
requestConfirmation,
|
||||
additionalWritableRoots,
|
||||
]);
|
||||
// We intentionally omit 'approvalPolicy' and 'confirmationPrompt' from the deps
|
||||
// so switching modes or showing confirmation dialogs doesn’t tear down the loop.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [model, config, requestConfirmation, additionalWritableRoots]);
|
||||
|
||||
// whenever loading starts/stops, reset or start a timer — but pause the
|
||||
// timer while a confirmation overlay is displayed so we don't trigger a
|
||||
@@ -350,9 +374,10 @@ export default function TerminalChat({
|
||||
const safePreview = preview.replace(/"/g, '\\"');
|
||||
const title = "Codex CLI";
|
||||
const cwd = PWD;
|
||||
exec(
|
||||
`osascript -e 'display notification "${safePreview}" with title "${title}" subtitle "${cwd}" sound name "Ping"'`,
|
||||
);
|
||||
spawn("osascript", [
|
||||
"-e",
|
||||
`display notification "${safePreview}" with title "${title}" subtitle "${cwd}" sound name "Ping"`,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,6 +478,7 @@ export default function TerminalChat({
|
||||
colorsByPolicy,
|
||||
agent,
|
||||
initialImagePaths,
|
||||
flexModeEnabled: Boolean(config.flexMode),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@@ -482,6 +508,26 @@ export default function TerminalChat({
|
||||
openModelOverlay={() => setOverlayMode("model")}
|
||||
openApprovalOverlay={() => setOverlayMode("approval")}
|
||||
openHelpOverlay={() => setOverlayMode("help")}
|
||||
openDiffOverlay={() => {
|
||||
const { isGitRepo, diff } = getGitDiff();
|
||||
let text: string;
|
||||
if (isGitRepo) {
|
||||
text = diff;
|
||||
} else {
|
||||
text = "`/diff` — _not inside a git repository_";
|
||||
}
|
||||
setItems((prev) => [
|
||||
...prev,
|
||||
{
|
||||
id: `diff-${Date.now()}`,
|
||||
type: "message",
|
||||
role: "system",
|
||||
content: [{ type: "input_text", text }],
|
||||
},
|
||||
]);
|
||||
// Ensure no overlay is shown.
|
||||
setOverlayMode("none");
|
||||
}}
|
||||
onCompact={handleCompact}
|
||||
active={overlayMode === "none"}
|
||||
interruptAgent={() => {
|
||||
@@ -516,6 +562,8 @@ export default function TerminalChat({
|
||||
agent.run(inputs, lastResponseId || "");
|
||||
return {};
|
||||
}}
|
||||
items={items}
|
||||
thinkingSeconds={thinkingSeconds}
|
||||
/>
|
||||
)}
|
||||
{overlayMode === "history" && (
|
||||
@@ -567,12 +615,20 @@ export default function TerminalChat({
|
||||
<ApprovalModeOverlay
|
||||
currentMode={approvalPolicy}
|
||||
onSelect={(newMode) => {
|
||||
agent?.cancel();
|
||||
setLoading(false);
|
||||
// update approval policy without cancelling an in-progress session
|
||||
if (newMode === approvalPolicy) {
|
||||
return;
|
||||
}
|
||||
// update state
|
||||
setApprovalPolicy(newMode as ApprovalPolicy);
|
||||
// update existing AgentLoop instance
|
||||
if (agentRef.current) {
|
||||
(
|
||||
agentRef.current as unknown as {
|
||||
approvalPolicy: ApprovalPolicy;
|
||||
}
|
||||
).approvalPolicy = newMode as ApprovalPolicy;
|
||||
}
|
||||
setItems((prev) => [
|
||||
...prev,
|
||||
{
|
||||
@@ -597,6 +653,13 @@ export default function TerminalChat({
|
||||
{overlayMode === "help" && (
|
||||
<HelpOverlay onExit={() => setOverlayMode("none")} />
|
||||
)}
|
||||
|
||||
{overlayMode === "diff" && (
|
||||
<DiffOverlay
|
||||
diffText={diffText}
|
||||
onExit={() => setOverlayMode("none")}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user