codex: scope default cancel selection to the current Claude session (#84)
Co-authored-by: VOIDXAI <VOIDXAI@users.noreply.github.com>
This commit is contained in:
parent
40d213d13f
commit
d216a5fdea
@ -924,7 +924,7 @@ async function handleCancel(argv) {
|
|||||||
|
|
||||||
const cwd = resolveCommandCwd(options);
|
const cwd = resolveCommandCwd(options);
|
||||||
const reference = positionals[0] ?? "";
|
const reference = positionals[0] ?? "";
|
||||||
const { workspaceRoot, job } = resolveCancelableJob(cwd, reference);
|
const { workspaceRoot, job } = resolveCancelableJob(cwd, reference, { env: process.env });
|
||||||
const existing = readStoredJob(workspaceRoot, job.id) ?? {};
|
const existing = readStoredJob(workspaceRoot, job.id) ?? {};
|
||||||
const threadId = existing.threadId ?? job.threadId ?? null;
|
const threadId = existing.threadId ?? job.threadId ?? null;
|
||||||
const turnId = existing.turnId ?? job.turnId ?? null;
|
const turnId = existing.turnId ?? job.turnId ?? null;
|
||||||
|
|||||||
@ -278,7 +278,7 @@ export function resolveResultJob(cwd, reference) {
|
|||||||
throw new Error("No finished Codex jobs found for this repository yet.");
|
throw new Error("No finished Codex jobs found for this repository yet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveCancelableJob(cwd, reference) {
|
export function resolveCancelableJob(cwd, reference, options = {}) {
|
||||||
const workspaceRoot = resolveWorkspaceRoot(cwd);
|
const workspaceRoot = resolveWorkspaceRoot(cwd);
|
||||||
const jobs = sortJobsNewestFirst(listJobs(workspaceRoot));
|
const jobs = sortJobsNewestFirst(listJobs(workspaceRoot));
|
||||||
const activeJobs = jobs.filter((job) => job.status === "queued" || job.status === "running");
|
const activeJobs = jobs.filter((job) => job.status === "queued" || job.status === "running");
|
||||||
@ -291,12 +291,18 @@ export function resolveCancelableJob(cwd, reference) {
|
|||||||
return { workspaceRoot, job: selected };
|
return { workspaceRoot, job: selected };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeJobs.length === 1) {
|
const sessionScopedActiveJobs = filterJobsForCurrentSession(activeJobs, options);
|
||||||
return { workspaceRoot, job: activeJobs[0] };
|
|
||||||
|
if (sessionScopedActiveJobs.length === 1) {
|
||||||
|
return { workspaceRoot, job: sessionScopedActiveJobs[0] };
|
||||||
}
|
}
|
||||||
if (activeJobs.length > 1) {
|
if (sessionScopedActiveJobs.length > 1) {
|
||||||
throw new Error("Multiple Codex jobs are active. Pass a job id to /codex:cancel.");
|
throw new Error("Multiple Codex jobs are active. Pass a job id to /codex:cancel.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getCurrentSessionId(options)) {
|
||||||
|
throw new Error("No active Codex jobs to cancel for this session.");
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error("No active Codex jobs to cancel.");
|
throw new Error("No active Codex jobs to cancel.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1471,6 +1471,109 @@ test("cancel stops an active background job and marks it cancelled", async (t) =
|
|||||||
assert.match(fs.readFileSync(logFile, "utf8"), /Cancelled by user/);
|
assert.match(fs.readFileSync(logFile, "utf8"), /Cancelled by user/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("cancel without a job id ignores active jobs from other Claude sessions", () => {
|
||||||
|
const workspace = makeTempDir();
|
||||||
|
const stateDir = resolveStateDir(workspace);
|
||||||
|
const jobsDir = path.join(stateDir, "jobs");
|
||||||
|
fs.mkdirSync(jobsDir, { recursive: true });
|
||||||
|
|
||||||
|
const logFile = path.join(jobsDir, "task-other.log");
|
||||||
|
fs.writeFileSync(logFile, "", "utf8");
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(stateDir, "state.json"),
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
config: { stopReviewGate: false },
|
||||||
|
jobs: [
|
||||||
|
{
|
||||||
|
id: "task-other",
|
||||||
|
status: "running",
|
||||||
|
title: "Codex Task",
|
||||||
|
jobClass: "task",
|
||||||
|
sessionId: "sess-other",
|
||||||
|
summary: "Other session run",
|
||||||
|
updatedAt: "2026-03-24T20:05:00.000Z",
|
||||||
|
logFile
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}\n`,
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
CODEX_COMPANION_SESSION_ID: "sess-current"
|
||||||
|
};
|
||||||
|
const status = run("node", [SCRIPT, "status", "--json"], {
|
||||||
|
cwd: workspace,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
assert.equal(status.status, 0, status.stderr);
|
||||||
|
assert.deepEqual(JSON.parse(status.stdout).running, []);
|
||||||
|
|
||||||
|
const cancel = run("node", [SCRIPT, "cancel", "--json"], {
|
||||||
|
cwd: workspace,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
assert.equal(cancel.status, 1);
|
||||||
|
assert.match(cancel.stderr, /No active Codex jobs to cancel for this session\./);
|
||||||
|
|
||||||
|
const state = JSON.parse(fs.readFileSync(path.join(stateDir, "state.json"), "utf8"));
|
||||||
|
assert.equal(state.jobs[0].status, "running");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cancel with a job id can still target an active job from another Claude session", () => {
|
||||||
|
const workspace = makeTempDir();
|
||||||
|
const stateDir = resolveStateDir(workspace);
|
||||||
|
const jobsDir = path.join(stateDir, "jobs");
|
||||||
|
fs.mkdirSync(jobsDir, { recursive: true });
|
||||||
|
|
||||||
|
const logFile = path.join(jobsDir, "task-other.log");
|
||||||
|
fs.writeFileSync(logFile, "", "utf8");
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(stateDir, "state.json"),
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
config: { stopReviewGate: false },
|
||||||
|
jobs: [
|
||||||
|
{
|
||||||
|
id: "task-other",
|
||||||
|
status: "running",
|
||||||
|
title: "Codex Task",
|
||||||
|
jobClass: "task",
|
||||||
|
sessionId: "sess-other",
|
||||||
|
summary: "Other session run",
|
||||||
|
updatedAt: "2026-03-24T20:05:00.000Z",
|
||||||
|
logFile
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}\n`,
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
CODEX_COMPANION_SESSION_ID: "sess-current"
|
||||||
|
};
|
||||||
|
const cancel = run("node", [SCRIPT, "cancel", "task-other", "--json"], {
|
||||||
|
cwd: workspace,
|
||||||
|
env
|
||||||
|
});
|
||||||
|
assert.equal(cancel.status, 0, cancel.stderr);
|
||||||
|
assert.equal(JSON.parse(cancel.stdout).jobId, "task-other");
|
||||||
|
|
||||||
|
const state = JSON.parse(fs.readFileSync(path.join(stateDir, "state.json"), "utf8"));
|
||||||
|
assert.equal(state.jobs[0].status, "cancelled");
|
||||||
|
});
|
||||||
|
|
||||||
test("cancel sends turn interrupt to the shared app-server before killing a brokered task", async () => {
|
test("cancel sends turn interrupt to the shared app-server before killing a brokered task", async () => {
|
||||||
const repo = makeTempDir();
|
const repo = makeTempDir();
|
||||||
const binDir = makeTempDir();
|
const binDir = makeTempDir();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user