diff --git a/skills/ocx/references/01_management_surface.md b/skills/ocx/references/01_management_surface.md index da019297d1..4cb391504a 100644 --- a/skills/ocx/references/01_management_surface.md +++ b/skills/ocx/references/01_management_surface.md @@ -750,7 +750,7 @@ JSON mode: `payload`. ### `ocx system codex-restart` -Restart the Codex app-server. +Restart the Codex desktop app and app-servers. | Method | Route | |---|---| @@ -758,7 +758,7 @@ Restart the Codex app-server. | Flag | Value | Meaning | |---|---|---| -| `--yes` | boolean | Required: restarts the operator's running Codex app-server. | +| `--yes` | boolean | Required: fully quits and relaunches the operator's Codex desktop app and restarts its app-servers. | | `--json` | boolean | Emit the restart result as JSON. | JSON mode: `payload`. diff --git a/src/cli/capabilities.ts b/src/cli/capabilities.ts index 36aa8124cc..309badbfd2 100644 --- a/src/cli/capabilities.ts +++ b/src/cli/capabilities.ts @@ -710,10 +710,10 @@ export const CAPABILITIES: readonly Capability[] = [ }, { command: ["system", "codex-restart"], - summary: "Restart the Codex app-server.", + summary: "Restart the Codex desktop app and app-servers.", routes: [{ method: "POST", path: "/api/system/codex-restart" }], flags: [ - { name: "--yes", value: "boolean", summary: "Required: restarts the operator's running Codex app-server." }, + { name: "--yes", value: "boolean", summary: "Required: fully quits and relaunches the operator's Codex desktop app and restarts its app-servers." }, { name: "--json", value: "boolean", summary: "Emit the restart result as JSON." }, ], mutates: true, diff --git a/src/cli/system-command.ts b/src/cli/system-command.ts index 03eb900887..a3b46b49d6 100644 --- a/src/cli/system-command.ts +++ b/src/cli/system-command.ts @@ -130,14 +130,14 @@ export async function handleSystemCommand(argv: string[], deps: RuntimeApiDeps = const args = [...rest]; const wantsJson = takeFlag(args, "--json"); rejectArgs(args, USAGE); printData(await runtimeRequest("/api/system/codex-app-server", {}, deps), wantsJson); } else if (sub === "codex-restart") { - // --yes required: this restarts the user's running Codex app-server, so it is exactly the - // class of action that must not happen because an agent guessed a subcommand. + // --yes required: this fully quits and relaunches the user's Codex desktop app as well as + // restarting app-servers; an agent guessing a subcommand must not interrupt that session. const args = [...rest]; const wantsJson = takeFlag(args, "--json"); const yes = takeFlag(args, "--yes"); - if (!yes) throw new CliUsageError("system codex-restart requires --yes", USAGE); + if (!yes) throw new CliUsageError("system codex-restart requires --yes: this fully quits and relaunches the Codex desktop app and restarts its app-servers", USAGE); rejectArgs(args, USAGE); - printData(await runtimeRequest("/api/system/codex-restart", { method: "POST" }, deps), wantsJson, ["Codex app-server restart requested."]); + printData(await runtimeRequest("/api/system/codex-restart", { method: "POST" }, deps), wantsJson, ["Codex desktop app and app-server restart requested."]); } else if (sub === "update") await update(rest, deps); else throw new CliUsageError(`unknown system command ${sub}`, USAGE); }); diff --git a/structure/clients/claude-desktop.md b/structure/clients/claude-desktop.md index 01a583c182..a8a7e5a719 100644 --- a/structure/clients/claude-desktop.md +++ b/structure/clients/claude-desktop.md @@ -17,6 +17,8 @@ The hub-side CLI dashboard uses the [management ingress address](../runtime.md#h Native main reauthentication follows the [CLI JSON output contract](../runtime.md#native-main-reauth-json-output). +The Codex restart command follows the [CLI restart scope contract](../runtime.md#cli-codex-restart-scope). + ## Connected Claude Desktop profiles The connection's local Codex readiness check follows the [selected-runtime probe contract](../runtime.md#remote-hub-hardening-ownership); general status hands its resolved command to this check instead of probing the version twice. diff --git a/structure/config.md b/structure/config.md index d00b36b06b..b6eef7c681 100644 --- a/structure/config.md +++ b/structure/config.md @@ -9,6 +9,8 @@ Hub management ingress also selects the [local dashboard address](runtime.md#hub Native main reauthentication follows the [CLI JSON output contract](runtime.md#native-main-reauth-json-output). +The Codex restart command follows the [CLI restart scope contract](runtime.md#cli-codex-restart-scope). + ## Config surface ### OpenCodex home and live process state diff --git a/structure/ops/docs-and-release.md b/structure/ops/docs-and-release.md index 0c0ffe38d0..65f8b7f717 100644 --- a/structure/ops/docs-and-release.md +++ b/structure/ops/docs-and-release.md @@ -11,6 +11,8 @@ The CLI default dashboard address follows the [management ingress bind](../runti Native main reauthentication follows the [CLI JSON output contract](../runtime.md#native-main-reauth-json-output). +The Codex restart command follows the [CLI restart scope contract](../runtime.md#cli-codex-restart-scope). + ## Public docs The public documentation site lives in `docs-site/` and is built with Astro + Starlight. English is diff --git a/structure/runtime.md b/structure/runtime.md index bd9ebbd561..3402619a40 100644 --- a/structure/runtime.md +++ b/structure/runtime.md @@ -20,6 +20,10 @@ Catalog-derived reasoning-level diagnostics are escaped only at the human-output `src/cli/account-main.ts` emits one JSON object to stdout when `ocx account main reauth --device --no-wait --json` succeeds. The human-readable `follow up:` line is emitted only without `--json`; `flowId` remains available for status polling. `tests/cli/cli-native-profile.test.ts` parses the complete captured stdout and preserves coverage of the human follow-up. +## CLI Codex restart scope + +`ocx system codex-restart` requests a full Codex desktop-app restart and app-server restarts through the management endpoint. `src/cli/capabilities.ts` names that scope in its summary and `--yes` description; `src/cli/system-command.ts` explains the desktop interruption when confirmation is missing and sends no restart request. Human output says the restart was requested, while `--json` preserves the complete server result, including skipped or refused desktop outcomes. + ## Hub management dashboard address When hub management ingress is enabled, `src/cli/dispatch.ts` opens the dashboard on the literal IPv4 loopback address and configured ingress port, matching the listener in `src/server/index.ts`. Other dashboard address selection is unchanged. diff --git a/tests/cli/cli-headless-parity.test.ts b/tests/cli/cli-headless-parity.test.ts index 9a11c696ac..a3d861d14a 100644 --- a/tests/cli/cli-headless-parity.test.ts +++ b/tests/cli/cli-headless-parity.test.ts @@ -20,6 +20,40 @@ import { repoPath } from "../helpers/repo-root"; type Recorded = { path: string; method: string; body: unknown }; const servers: Array> = []; +describe("ocx system codex-restart confirmation", () => { + test("names the desktop interruption before any unconfirmed request", async () => { + const { requests, deps } = fakeRuntime(); + const errors = spyOn(console, "error").mockImplementation(() => {}); + try { + expect(await handleSystemCommand(["codex-restart"], deps)).toBe(2); + expect(requests).toHaveLength(0); + const warning = errors.mock.calls.flat().join(" "); + expect(warning).toContain("requires --yes"); + expect(warning).toContain("fully quits and relaunches the Codex desktop app"); + } finally { errors.mockRestore(); } + }); + + test.each([false, true])("preserves requested versus completed outcomes (json=%s)", async wantsJson => { + // A skipped Desktop outcome must survive JSON output; this fixture cannot restart processes. + const result = { success: true, code: "nothing_running", requested: [], stopped: [], + desktopApp: { attempted: false, relaunch: "skipped", reason: "self_ancestry" } }; + const { requests, deps } = fakeRuntime(() => result); + const output = spyOn(console, "log").mockImplementation(() => {}); + try { + const argv = ["codex-restart", "--yes", ...(wantsJson ? ["--json"] : [])]; + expect(await handleSystemCommand(argv, deps)).toBe(0); + expect(requests).toEqual([{ path: "/api/system/codex-restart", method: "POST", body: null }]); + const text = output.mock.calls.flat().join("\n"); + if (wantsJson) expect(JSON.parse(text)).toEqual(result); + else { + expect(text).toContain("Codex desktop app"); + expect(text).toContain("restart requested."); + expect(text).not.toContain("restarted"); + } + } finally { output.mockRestore(); } + }); +}); + describe("ocx system settings client compaction", () => { test("persists the explicit boolean through the shared settings endpoint", async () => { const { requests, deps } = fakeRuntime((_req, body) => ({ ok: true, ...body }));