Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,286 changes: 1,197 additions & 89 deletions apps/codex-plus-manager/src-tauri/src/commands.rs

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions apps/codex-plus-manager/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ import {
import { subscribeLanguage, getLanguage, t, tf, toggleLanguage } from "@/i18n";
import { vlmTestTranslation } from "./vlm-test-translation";
import {
multiLaunchInstanceCanRestart,
multiLaunchInstanceCanStop,
multiLaunchInstanceFailure,
multiLaunchInstanceLabel as formatMultiLaunchInstanceLabel,
multiLaunchInstanceRunning,
Expand Down Expand Up @@ -2797,7 +2799,7 @@ export function App() {
// 界面要给出「启动失败 + 原因」,只留运行中的会让它彻底消失。
setMultiLaunchInstances(
Array.isArray(result.instances)
? result.instances.filter((instance) => multiLaunchInstanceRunning(instance) || Boolean(multiLaunchInstanceFailure(instance)))
? result.instances.filter((instance) => multiLaunchInstanceStatus(instance) !== "stopped")
: [],
);
if (!silent && !isSuccessStatus(result.status)) showNotice(t("当前运行实例"), result.message, result.status);
Expand Down Expand Up @@ -7557,7 +7559,7 @@ function CodexMultiLaunchScreen({
<div className="multi-launch-instance-list">
{actions.multiLaunchInstances.map((instance) => {
const status = multiLaunchInstanceStatus(instance);
const running = multiLaunchInstanceRunning(instance);
const canStop = multiLaunchInstanceCanStop(instance);
const pending = actions.multiLaunchActionPending === instance.instanceId;
const label = formatMultiLaunchInstanceLabel(instance, { main: t("主 Codex"), fallback: t("Codex 实例") });
// 启动失败的原因必须让用户看得见:Windows 上多开曾经完全不吭声。
Expand All @@ -7567,7 +7569,7 @@ function CodexMultiLaunchScreen({
typeof instance.pid === "number" ? `PID ${instance.pid}` : "",
typeof instance.debugPort === "number" ? `CDP ${instance.debugPort}` : "",
typeof instance.helperPort === "number" ? `API ${instance.helperPort}` : "",
failure ? `${t("启动失败")}:${failure}` : "",
failure ? `${instance.launchResult?.status === "cleanup_failed" ? t("失败") : t("启动失败")}:${failure}` : "",
].filter(Boolean).join(" · ");
return (
<div className="multi-launch-instance-card" data-instance-id={instance.instanceId} key={instance.instanceId}>
Expand All @@ -7579,7 +7581,7 @@ function CodexMultiLaunchScreen({
<UiBadge className={`multi-launch-instance-status ${statusClass(status)}`} variant="secondary">{statusLabel(status)}</UiBadge>
<span className="relay-card-actions">
<Button
disabled={pending}
disabled={!multiLaunchInstanceCanRestart(instance) || pending}
onClick={() => void actions.restartMultiLaunchInstance(instance)}
size="sm"
title={t("重启此 Codex 实例")}
Expand All @@ -7588,10 +7590,10 @@ function CodexMultiLaunchScreen({
{pending ? t("处理中") : t("重启")}
</Button>
<Button
disabled={!running || pending}
disabled={!canStop || pending}
onClick={() => void actions.stopMultiLaunchInstance(instance)}
size="sm"
title={running ? t("结束此 Codex 实例") : t("实例当前未运行")}
title={canStop ? t("结束此 Codex 实例") : t("实例当前未运行")}
variant="secondary"
>
<PowerOff className="h-4 w-4" />
Expand Down
22 changes: 21 additions & 1 deletion apps/codex-plus-manager/src/multi-launch-model.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
multiLaunchInstanceCanRestart,
multiLaunchInstanceCanStop,
multiLaunchInstanceFailure,
multiLaunchInstanceLabel,
multiLaunchInstanceRunning,
Expand All @@ -16,6 +18,17 @@ const instance = (patch: Partial<MultiLaunchInstance> = {}): MultiLaunchInstance
});

describe("Codex 多开运行实例模型", () => {
it("启动期间禁止结束和重启,其他状态保留原有操作", () => {
const starting = instance({ status: " STARTING ", running: true });
assert.equal(multiLaunchInstanceCanStop(starting), false);
assert.equal(multiLaunchInstanceCanRestart(starting), false);
for (const status of ["running", "unknown", "failed"]) {
assert.equal(multiLaunchInstanceCanStop(instance({ status })), true);
assert.equal(multiLaunchInstanceCanRestart(instance({ status })), true);
}
assert.equal(multiLaunchInstanceCanStop(instance({ status: "stopped" })), false);
assert.equal(multiLaunchInstanceCanRestart(instance({ status: "stopped" })), true);
});
it("主 Codex 优先使用主实例标签", () => {
assert.equal(multiLaunchInstanceLabel(instance({ isMain: true, profileName: "旧名称" })), "主 Codex");
});
Expand All @@ -28,7 +41,7 @@ describe("Codex 多开运行实例模型", () => {

it("状态归一化并识别可结束的运行态", () => {
assert.equal(multiLaunchInstanceStatus(instance({ status: " RUNNING " })), "running");
assert.equal(multiLaunchInstanceRunning(instance({ status: "starting" })), true);
assert.equal(multiLaunchInstanceRunning(instance({ status: "starting" })), false);
assert.equal(multiLaunchInstanceRunning(instance({ status: "running_degraded" })), true);
assert.equal(multiLaunchInstanceRunning(instance({ status: "stopped", running: true })), true);
assert.equal(multiLaunchInstanceRunning(instance({ status: "stopped" })), false);
Expand All @@ -42,6 +55,13 @@ describe("Codex 多开运行实例模型", () => {
launchResult: { status: "failed", message: "调试端口 9456 未就绪" },
});
assert.equal(multiLaunchInstanceFailure(failed), "调试端口 9456 未就绪");
assert.equal(
multiLaunchInstanceFailure(instance({
status: "unknown", running: false,
launchResult: { status: "cleanup_failed", message: "实例清理未完成" },
})),
"实例清理未完成",
);

assert.equal(
multiLaunchInstanceFailure(instance({ status: "failed", running: false, launchResult: { status: "failed" } })),
Expand Down
13 changes: 11 additions & 2 deletions apps/codex-plus-manager/src/multi-launch-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@ export function multiLaunchInstanceStatus(instance: MultiLaunchInstance): string

export function multiLaunchInstanceRunning(instance: MultiLaunchInstance): boolean {
if (typeof instance.running === "boolean") return instance.running;
return ["running", "running_degraded", "starting", "ready", "connected", "ok"].includes(multiLaunchInstanceStatus(instance));
return ["running", "running_degraded", "ready", "connected", "ok"].includes(multiLaunchInstanceStatus(instance));
}

export function multiLaunchInstanceCanStop(instance: MultiLaunchInstance): boolean {
const status = multiLaunchInstanceStatus(instance);
return status !== "starting" && (multiLaunchInstanceRunning(instance) || status === "unknown" || status === "failed");
}

export function multiLaunchInstanceCanRestart(instance: MultiLaunchInstance): boolean {
return multiLaunchInstanceStatus(instance) !== "starting";
}

/**
Expand All @@ -50,7 +59,7 @@ export function multiLaunchInstanceRunning(instance: MultiLaunchInstance): boole
export function multiLaunchInstanceFailure(instance: MultiLaunchInstance): string {
if (multiLaunchInstanceRunning(instance)) return "";
const result = instance.launchResult;
if (!result || result.status !== "failed") return "";
if (!result || !["failed", "cleanup_failed"].includes(result.status ?? "")) return "";
return result.message?.trim() || "Codex 实例启动失败,原因未记录。";
}

Expand Down
Loading
Loading