Merge pull request '修正 Codex Action 重複輸出最後回答' (#3) from ai-review-resolve/20260624102518 into develop
Reviewed-on: actions/codex#3
This commit was merged in pull request #3.
This commit is contained in:
@@ -1,10 +1,16 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"role": "Bard",
|
||||||
|
"location": "app/main.js:241",
|
||||||
|
"problem": "`runCodex` 中 `resultOutput` 的計算邏輯過於緊湊且不易維護,且直接進行檔案同步讀取,若檔案過大可能阻塞 Node.js 事件循環。",
|
||||||
|
"suggestion": "建議拆解複雜的邏輯為獨立的輔助函數,並改用非同步的檔案讀取方式。"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"level": "info",
|
"level": "info",
|
||||||
"role": "Bard",
|
"role": "Bard",
|
||||||
"problem": "setupAuth 函式職責過於繁雜。",
|
"location": "app/main.js:321",
|
||||||
"suggestion": "將鎖定機制與驗證機制拆分為獨立輔助函式。",
|
"problem": "確保字串結尾換行的樣板程式碼邏輯重複出現。",
|
||||||
"location": "app/main.js:283",
|
"suggestion": "建立 `ensureNewline` 共用工具函式。"
|
||||||
"is_new": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+15
-6
@@ -183,11 +183,13 @@ function readExecutionConfig() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function codexExecArgs(model, prompt) {
|
function codexExecArgs(model, prompt, lastMessageFile) {
|
||||||
return [
|
return [
|
||||||
"exec",
|
"exec",
|
||||||
"--dangerously-bypass-approvals-and-sandbox",
|
"--dangerously-bypass-approvals-and-sandbox",
|
||||||
"--skip-git-repo-check",
|
"--skip-git-repo-check",
|
||||||
|
"--output-last-message",
|
||||||
|
lastMessageFile,
|
||||||
"--model",
|
"--model",
|
||||||
model,
|
model,
|
||||||
prompt,
|
prompt,
|
||||||
@@ -197,7 +199,10 @@ function codexExecArgs(model, prompt) {
|
|||||||
function runCodex(model, prompt) {
|
function runCodex(model, prompt) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const { timeoutMs, outputLimitBytes, workspace } = readExecutionConfig();
|
const { timeoutMs, outputLimitBytes, workspace } = readExecutionConfig();
|
||||||
const args = codexExecArgs(model, prompt);
|
const tempDir = makeTempDir(process.env.CODEX_HOME || "/root/.codex");
|
||||||
|
const lastMessageFile = path.join(tempDir, "last-message.txt");
|
||||||
|
tempFiles.trackFile(lastMessageFile);
|
||||||
|
const args = codexExecArgs(model, prompt, lastMessageFile);
|
||||||
|
|
||||||
// This Docker Action runs inside an ephemeral CI container where Codex must be
|
// This Docker Action runs inside an ephemeral CI container where Codex must be
|
||||||
// able to edit the checked-out workspace without interactive approvals.
|
// able to edit the checked-out workspace without interactive approvals.
|
||||||
@@ -210,9 +215,6 @@ function runCodex(model, prompt) {
|
|||||||
output.append(Buffer.from(`Codex execution timed out after ${timeoutMs} ms.\n`));
|
output.append(Buffer.from(`Codex execution timed out after ${timeoutMs} ms.\n`));
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
|
|
||||||
child.stdout.pipe(process.stdout);
|
|
||||||
child.stderr.pipe(process.stdout);
|
|
||||||
|
|
||||||
child.stdout.on("data", (chunk) => {
|
child.stdout.on("data", (chunk) => {
|
||||||
output.append(chunk);
|
output.append(chunk);
|
||||||
});
|
});
|
||||||
@@ -235,7 +237,11 @@ function runCodex(model, prompt) {
|
|||||||
output.append(Buffer.from(`Codex process terminated by signal ${signal}.\n`));
|
output.append(Buffer.from(`Codex process terminated by signal ${signal}.\n`));
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve({ status: code ?? 1, output: output.toString() });
|
const lastMessage = fs.existsSync(lastMessageFile) ? fs.readFileSync(lastMessageFile, "utf8") : null;
|
||||||
|
const diagnosticOutput = output.toString();
|
||||||
|
const resultOutput = code === 0 && lastMessage !== null ? lastMessage : diagnosticOutput || lastMessage || "";
|
||||||
|
|
||||||
|
resolve({ status: code ?? 1, output: resultOutput });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -312,6 +318,9 @@ function setupAuth(oauth, codexHome) {
|
|||||||
async function runCodexAction({ oauth, model, codexHome, prompt }) {
|
async function runCodexAction({ oauth, model, codexHome, prompt }) {
|
||||||
const lockHandle = setupAuth(oauth, codexHome);
|
const lockHandle = setupAuth(oauth, codexHome);
|
||||||
const result = await runCodex(model, prompt);
|
const result = await runCodex(model, prompt);
|
||||||
|
if (result.output) {
|
||||||
|
process.stdout.write(result.output.endsWith("\n") ? result.output : `${result.output}\n`);
|
||||||
|
}
|
||||||
appendGithubOutput(result.status === 0 ? "completed" : "failed", result.output);
|
appendGithubOutput(result.status === 0 ? "completed" : "failed", result.output);
|
||||||
|
|
||||||
if (lockHandle !== undefined) {
|
if (lockHandle !== undefined) {
|
||||||
|
|||||||
@@ -56,6 +56,26 @@ SH
|
|||||||
chmod +x "$dir/codex"
|
chmod +x "$dir/codex"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
make_fake_transcript_codex() {
|
||||||
|
local dir="$1"
|
||||||
|
|
||||||
|
cat > "$dir/codex" <<'SH'
|
||||||
|
#!/bin/sh
|
||||||
|
last_message_file=""
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
if [ "$1" = "--output-last-message" ]; then
|
||||||
|
shift
|
||||||
|
last_message_file="$1"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' 'codex' 'final answer' 'tokens used' '123' 'final answer' >&2
|
||||||
|
printf '%s\n' 'final answer' > "$last_message_file"
|
||||||
|
SH
|
||||||
|
chmod +x "$dir/codex"
|
||||||
|
}
|
||||||
|
|
||||||
encoded_json() {
|
encoded_json() {
|
||||||
printf '%s' "$1" | base64 | tr -d '\n'
|
printf '%s' "$1" | base64 | tr -d '\n'
|
||||||
}
|
}
|
||||||
@@ -269,6 +289,20 @@ test_codex_output_truncation() {
|
|||||||
rm -rf "$tmpdir"
|
rm -rf "$tmpdir"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_last_message_output_without_transcript_duplicate() {
|
||||||
|
local tmpdir
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
make_fake_transcript_codex "$tmpdir"
|
||||||
|
|
||||||
|
run_entrypoint "$tmpdir" OAUTH="$(encoded_json '{}')" MODEL="gpt-test" PROMPT="hello"
|
||||||
|
|
||||||
|
[[ "$(grep -c "final answer" "$tmpdir/stdout")" -eq 1 ]] || die "stdout printed final answer more than once"
|
||||||
|
grep -q "status=completed" "$tmpdir/github-output" || die "last message completed status"
|
||||||
|
[[ "$(grep -c "final answer" "$tmpdir/github-output")" -eq 1 ]] || die "github output included transcript duplicate"
|
||||||
|
! grep -q "tokens used" "$tmpdir/github-output" || die "github output included codex transcript"
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
}
|
||||||
|
|
||||||
test_invalid_execution_config_uses_defaults() {
|
test_invalid_execution_config_uses_defaults() {
|
||||||
local tmpdir
|
local tmpdir
|
||||||
tmpdir="$(mktemp -d)"
|
tmpdir="$(mktemp -d)"
|
||||||
@@ -299,6 +333,7 @@ test_missing_codex_command_output
|
|||||||
test_codex_timeout_output
|
test_codex_timeout_output
|
||||||
test_codex_signal_output
|
test_codex_signal_output
|
||||||
test_codex_output_truncation
|
test_codex_output_truncation
|
||||||
|
test_last_message_output_without_transcript_duplicate
|
||||||
test_invalid_execution_config_uses_defaults
|
test_invalid_execution_config_uses_defaults
|
||||||
|
|
||||||
echo "entrypoint tests passed"
|
echo "entrypoint tests passed"
|
||||||
|
|||||||
Reference in New Issue
Block a user