Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0423ccd3f | |||
| 8e403f9d4b | |||
| 6360899e1c | |||
| 65486f2878 | |||
| 64dc174d83 | |||
| a1266348d6 | |||
| 1a79ae57ec | |||
| 19642ad95f | |||
| 9cb4fe4099 | |||
| cf6f8515d8 | |||
| f4d65d9641 |
@@ -5,13 +5,13 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Codex plugins to use in Claude Code for delegation and code review.",
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "codex",
|
||||
"description": "Use Codex from Claude Code to review code or delegate tasks.",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"author": {
|
||||
"name": "OpenAI"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Pull Request CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Codex CLI
|
||||
run: npm install -g @openai/codex
|
||||
|
||||
- name: Run test suite
|
||||
run: npm test
|
||||
|
||||
- name: Run build
|
||||
run: npm run build
|
||||
@@ -33,6 +33,12 @@ Install the plugin:
|
||||
/plugin install codex@openai-codex
|
||||
```
|
||||
|
||||
Reload plugins:
|
||||
|
||||
```bash
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openai/codex-plugin-cc",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openai/codex-plugin-cc",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openai/codex-plugin-cc",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Use Codex from Claude Code to review code or delegate tasks.",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "codex",
|
||||
"version": "1.0.2",
|
||||
"description": "Use Codex from Claude Code to review code or delegate tasks.",
|
||||
"author": {
|
||||
"name": "OpenAI"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description: Delegate investigation, an explicit fix request, or follow-up rescue work to the Codex rescue subagent
|
||||
argument-hint: "[--background|--wait] [--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [what Codex should investigate, solve, or continue]"
|
||||
context: fork
|
||||
allowed-tools: Bash(node:*)
|
||||
allowed-tools: Bash(node:*), AskUserQuestion
|
||||
---
|
||||
|
||||
Route this request to the `codex:codex-rescue` subagent.
|
||||
|
||||
@@ -14,6 +14,7 @@ import { spawn } from "node:child_process";
|
||||
import readline from "node:readline";
|
||||
import { parseBrokerEndpoint } from "./broker-endpoint.mjs";
|
||||
import { ensureBrokerSession } from "./broker-lifecycle.mjs";
|
||||
import { terminateProcessTree } from "./process.mjs";
|
||||
|
||||
const PLUGIN_MANIFEST_URL = new URL("../../.claude-plugin/plugin.json", import.meta.url);
|
||||
const PLUGIN_MANIFEST = JSON.parse(fs.readFileSync(PLUGIN_MANIFEST_URL, "utf8"));
|
||||
@@ -188,7 +189,9 @@ class SpawnedCodexAppServerClient extends AppServerClientBase {
|
||||
this.proc = spawn("codex", ["app-server"], {
|
||||
cwd: this.cwd,
|
||||
env: this.options.env,
|
||||
stdio: ["pipe", "pipe", "pipe"]
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
shell: process.platform === "win32" ? (process.env.SHELL || true) : false,
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
this.proc.stdout.setEncoding("utf8");
|
||||
@@ -237,8 +240,20 @@ class SpawnedCodexAppServerClient extends AppServerClientBase {
|
||||
if (this.proc && !this.proc.killed) {
|
||||
this.proc.stdin.end();
|
||||
setTimeout(() => {
|
||||
if (this.proc && !this.proc.killed) {
|
||||
this.proc.kill("SIGTERM");
|
||||
if (this.proc && !this.proc.killed && this.proc.exitCode === null) {
|
||||
// On Windows with shell: true, the direct child is cmd.exe.
|
||||
// Use terminateProcessTree to kill the entire tree including
|
||||
// the grandchild node process.
|
||||
if (process.platform === "win32") {
|
||||
try {
|
||||
terminateProcessTree(this.proc.pid);
|
||||
} catch {
|
||||
// Best-effort cleanup inside an unref'd timer — swallow errors
|
||||
// to avoid crashing the host process during shutdown.
|
||||
}
|
||||
} else {
|
||||
this.proc.kill("SIGTERM");
|
||||
}
|
||||
}
|
||||
}, 50).unref?.();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ export function runCommand(command, args = [], options = {}) {
|
||||
env: options.env,
|
||||
encoding: "utf8",
|
||||
input: options.input,
|
||||
stdio: options.stdio ?? "pipe"
|
||||
stdio: options.stdio ?? "pipe",
|
||||
shell: process.platform === "win32" ? (process.env.SHELL || true) : false,
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,8 +2,9 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const ROOT = "/Users/dkundel/code/codex-plugin";
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const PLUGIN_ROOT = path.join(ROOT, "plugins", "codex");
|
||||
|
||||
function read(relativePath) {
|
||||
@@ -89,6 +90,7 @@ test("rescue command absorbs continue semantics", () => {
|
||||
const runtimeSkill = read("skills/codex-cli-runtime/SKILL.md");
|
||||
|
||||
assert.match(rescue, /The final user-visible response must be Codex's output verbatim/i);
|
||||
assert.match(rescue, /allowed-tools:\s*Bash\(node:\*\),\s*AskUserQuestion/);
|
||||
assert.match(rescue, /--background\|--wait/);
|
||||
assert.match(rescue, /--resume\|--fresh/);
|
||||
assert.match(rescue, /--model <model\|spark>/);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
import { writeExecutable } from "./helpers.mjs";
|
||||
|
||||
@@ -465,7 +467,7 @@ rl.on("line", (line) => {
|
||||
}
|
||||
}
|
||||
send({ method: "turn/completed", params: { threadId: thread.id, turn: buildTurn(turnId, "completed") } });
|
||||
}, 400);
|
||||
}, 5000);
|
||||
interruptibleTurns.set(turnId, { threadId: thread.id, timer });
|
||||
} else if (BEHAVIOR === "slow-task") {
|
||||
emitTurnCompletedLater(thread.id, turnId, items, 400);
|
||||
@@ -507,11 +509,19 @@ rl.on("line", (line) => {
|
||||
});
|
||||
`;
|
||||
writeExecutable(scriptPath, source);
|
||||
|
||||
// On Windows, npm global binaries are invoked via .cmd wrappers.
|
||||
// Create a codex.cmd so the fake binary is discoverable by spawn with shell: true.
|
||||
if (process.platform === "win32") {
|
||||
const cmdWrapper = `@echo off\r\nnode "%~dp0codex" %*\r\n`;
|
||||
fs.writeFileSync(path.join(binDir, "codex.cmd"), cmdWrapper, { encoding: "utf8" });
|
||||
}
|
||||
}
|
||||
|
||||
export function buildEnv(binDir) {
|
||||
const sep = process.platform === "win32" ? ";" : ":";
|
||||
return {
|
||||
...process.env,
|
||||
PATH: `${binDir}:${process.env.PATH}`
|
||||
PATH: `${binDir}${sep}${process.env.PATH}`
|
||||
};
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
export function makeTempDir(prefix = "codex-plugin-test-") {
|
||||
@@ -16,7 +17,9 @@ export function run(command, args, options = {}) {
|
||||
cwd: options.cwd,
|
||||
env: options.env,
|
||||
encoding: "utf8",
|
||||
input: options.input
|
||||
input: options.input,
|
||||
shell: process.platform === "win32" && !path.isAbsolute(command),
|
||||
windowsHide: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ import path from "node:path";
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { buildEnv, installFakeCodex } from "./fake-codex-fixture.mjs";
|
||||
import { initGitRepo, makeTempDir, run } from "./helpers.mjs";
|
||||
import { loadBrokerSession } from "../plugins/codex/scripts/lib/broker-lifecycle.mjs";
|
||||
import { resolveStateDir } from "../plugins/codex/scripts/lib/state.mjs";
|
||||
|
||||
const ROOT = "/Users/dkundel/code/codex-plugin";
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const PLUGIN_ROOT = path.join(ROOT, "plugins", "codex");
|
||||
const SCRIPT = path.join(PLUGIN_ROOT, "scripts", "codex-companion.mjs");
|
||||
const STOP_HOOK = path.join(PLUGIN_ROOT, "scripts", "stop-review-gate-hook.mjs");
|
||||
@@ -553,7 +554,7 @@ test("task --background enqueues a detached worker and exposes per-job status",
|
||||
|
||||
const waitedStatus = run(
|
||||
"node",
|
||||
[SCRIPT, "status", launchPayload.jobId, "--wait", "--timeout-ms", "5000", "--json"],
|
||||
[SCRIPT, "status", launchPayload.jobId, "--wait", "--timeout-ms", "15000", "--json"],
|
||||
{
|
||||
cwd: repo,
|
||||
env: buildEnv(binDir)
|
||||
@@ -1275,7 +1276,7 @@ test("cancel sends turn interrupt to the shared app-server before killing a brok
|
||||
return job;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}, { timeoutMs: 15000 });
|
||||
|
||||
const cancelResult = run("node", [SCRIPT, "cancel", jobId, "--json"], {
|
||||
cwd: repo,
|
||||
|
||||
Reference in New Issue
Block a user