Skip to content
Merged
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,13 +526,22 @@ Default local verification path:
npm run ci
npm run test:quick
npm run test
npm run mutation:gate
npm run bench:e2e:speed:gate
```

`npm run ci` is now the hosted-aligned local fast gate. Use
`npm run ci:strict`, `npm run docs:check`, `bash scripts/check_repo_hygiene.sh`,
`npm run scan:workflow-security`, `npm run scan:trivy`, and
`npm run security:scan:closeout` only when you intentionally want the stricter
closeout/manual layers.
`npm run mutation:gate` is the root mutation entrypoint for the existing
Orchestrator mutation profiles, `npm run bench:e2e:speed:gate` is the
fail-closed benchmark gate that evaluates a real benchmark summary once a run
has produced one, and `npm run coverage:repo` now points to the active
coverage runner that prepares subproject dependencies before generating fresh
repo-level coverage receipts. Use `npm run coverage:repo:aggregate` only when
you intentionally want to re-aggregate already-existing coverage artifacts.

Current CI contract has five layers only:

Expand Down
4 changes: 3 additions & 1 deletion apps/dashboard/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ if (pool !== requestedPool) {
}
const shouldEmitHtmlCoverage = !process.env.CI || process.env.CORTEXPILOT_COVERAGE_HTML === "1";
const coverageReporter = shouldEmitHtmlCoverage ? ["text", "html", "json-summary"] : ["text", "json-summary"];
const coverageReportsDirectory = path.resolve(process.cwd(), "coverage");
const coverageReportsDirectory = process.env.CORTEXPILOT_DASHBOARD_COVERAGE_DIR
? path.resolve(process.env.CORTEXPILOT_DASHBOARD_COVERAGE_DIR)
: path.resolve(process.cwd(), "coverage");
const coverageClean = !serialCoverageMode;
const coverageProcessingConcurrency = serialCoverageMode ? 1 : undefined;
const testTimeout = process.env.CI ? 45000 : 15000;
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/scripts/playwright-tempdir.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function sanitizeScope(scope) {
function resolveTempRoot(scriptDir) {
const runnerTemp = normalizeValue(process.env.RUNNER_TEMP);
if (runnerTemp) return resolve(runnerTemp);
return resolve(scriptDir, "..", "..", "..", ".runtime-cache", "temp");
return resolve(scriptDir, "..", "..", "..", ".runtime-cache", "cache", "tmp");
}

export function configurePlaywrightTempDir(scope) {
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/components/chain/ChainPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ describe("ChainPanel", () => {
/>
);

fireEvent.click(screen.getByRole("button", { name: "简洁视图" }));
fireEvent.click(screen.getByRole("button", { name: "详细视图" }));
fireEvent.click(screen.getByRole("button", { name: "Chain 优先" }));
fireEvent.click(screen.getByRole("button", { name: "Compact view" }));
fireEvent.click(screen.getByRole("button", { name: "Detailed view" }));
fireEvent.click(screen.getByRole("button", { name: "Chain first" }));

expect(setChainDisplayMode).toHaveBeenCalledWith("compact");
expect(setChainDisplayMode).toHaveBeenCalledWith("detail");
Expand All @@ -79,7 +79,7 @@ describe("ChainPanel", () => {
/>
);

const legend = screen.getByLabelText("节点状态说明");
const legend = screen.getByLabelText("Node status legend");
const items = legend.querySelectorAll("li");
expect(items).toHaveLength(2);
expect(items[0]).toHaveClass("is-active");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import { DesktopCopilotPanel } from "./DesktopCopilotPanel";

describe("DesktopCopilotPanel", () => {
it("renders operator-brief truth surfaces and grounded takeaways after generation", async () => {
const loadBrief = vi.fn().mockResolvedValue({
report_type: "operator_copilot_brief",
status: "AVAILABLE",
scope: "run_detail",
subject_id: "run-123",
summary: "The operator should compare the staged diff before accepting the run.",
likely_cause: "The last proof pack is stale.",
compare_takeaway: "Compare the staged diff against the last approved run.",
proof_takeaway: "Refresh the proof pack before asking for review.",
incident_takeaway: "Treat stale proof as an incident until it is re-generated.",
queue_takeaway: "Keep the queue paused until proof is current.",
approval_takeaway: "Approval should wait for a fresh proof receipt.",
used_truth_surfaces: ["run_detail", "", "proof_pack"],
limitations: ["review not started", " "],
recommended_actions: ["Refresh proof", "Request review", " "],
top_risks: ["stale-proof", "", "queue drift"],
});

render(
<DesktopCopilotPanel
intro="Only grounded control-plane truth belongs here."
questionSet={["What is blocked?", "What should the operator do next?"]}
loadBrief={loadBrief}
/>,
);

expect(screen.getByText("Only grounded control-plane truth belongs here.")).toBeInTheDocument();
expect(screen.getByText("What is blocked?")).toBeInTheDocument();
expect(screen.getByText("What should the operator do next?")).toBeInTheDocument();
expect(screen.getByText("On demand")).toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "Generate operator brief" }));

expect(await screen.findByText("Grounded brief")).toBeInTheDocument();
expect(await screen.findByText("The operator should compare the staged diff before accepting the run.")).toBeInTheDocument();
expect(screen.getByText("The last proof pack is stale.")).toBeInTheDocument();
expect(screen.getByText("Scope: run_detail")).toBeInTheDocument();
expect(screen.getByText("Subject: run-123")).toBeInTheDocument();
expect(screen.getByText("Truth surfaces: run_detail | proof_pack")).toBeInTheDocument();
expect(screen.getByText("Limitations: review not started")).toBeInTheDocument();
expect(screen.getByText("Compare the staged diff against the last approved run.")).toBeInTheDocument();
expect(screen.getByText("Keep the queue paused until proof is current.")).toBeInTheDocument();
expect(screen.getByText("Refresh proof")).toBeInTheDocument();
expect(screen.getByText("queue drift")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Regenerate brief" })).toBeInTheDocument();

expect(loadBrief).toHaveBeenCalledTimes(1);
});

it("covers flight-plan fallback labels and empty action/risk lists", async () => {
const loadBrief = vi.fn().mockResolvedValue({
report_type: "flight_plan_copilot_brief",
status: "UNAVAILABLE",
summary: "The plan is still advisory because execution has not started yet.",
risk_takeaway: "Approval is still blocked on a missing operator confirmation.",
capability_takeaway: "Runtime capability is unresolved until the runner binds.",
approval_takeaway: "An operator must confirm the start gate before execution.",
used_truth_surfaces: ["execution_plan_preview"],
recommended_actions: ["", " "],
top_risks: [],
limitations: undefined,
});

render(<DesktopCopilotPanel title="Flight plan panel" intro={undefined} questionSet={["Why this plan?"]} loadBrief={loadBrief} />);

fireEvent.click(screen.getByRole("button", { name: "Generate operator brief" }));

expect(await screen.findByText("Unavailable")).toBeInTheDocument();
expect(screen.getByText("Scope: flight_plan")).toBeInTheDocument();
expect(screen.getByText("Subject: execution_plan_report")).toBeInTheDocument();
expect(screen.getByText("Truth surfaces: execution_plan_preview")).toBeInTheDocument();
expect(screen.getByText("Limitations: -")).toBeInTheDocument();
expect(screen.getAllByText("Approval is still blocked on a missing operator confirmation.").length).toBeGreaterThan(0);
expect(screen.getByText("This brief stays advisory until a run actually starts.")).toBeInTheDocument();
expect(screen.getByText("No recommended actions were returned.")).toBeInTheDocument();
expect(screen.getByText("No explicit risks were returned.")).toBeInTheDocument();
});

it("surfaces load failures without leaving the panel in generating state", async () => {
const loadBrief = vi.fn().mockRejectedValue("brief backend unavailable");

render(<DesktopCopilotPanel questionSet={["Why did this fail?"]} loadBrief={loadBrief} />);

fireEvent.click(screen.getByRole("button", { name: "Generate operator brief" }));

expect(await screen.findByText("brief backend unavailable")).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByRole("button", { name: "Generate operator brief" })).toBeEnabled();
});
});
});
22 changes: 16 additions & 6 deletions apps/desktop/src/hooks/useDesktopData.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ describe("useDesktopData", () => {
const user = userEvent.setup();
render(<HookHarness activePage="overview" />);
await waitFor(() => {
expect(screen.getByTestId("live-error")).toHaveTextContent("总览数据拉取失败");
expect(screen.getByTestId("live-error")).toHaveTextContent(
"Failed to refresh overview data: the service is temporarily unavailable. Try again in a moment.",
);
});

overviewFail = false;
Expand All @@ -131,7 +133,9 @@ describe("useDesktopData", () => {
);
render(<HookHarness activePage="sessions" />);
await waitFor(() => {
expect(screen.getByTestId("live-error")).toHaveTextContent("会话列表拉取失败");
expect(screen.getByTestId("live-error")).toHaveTextContent(
"Failed to refresh the session list: the service is temporarily unavailable. Try again in a moment.",
);
});
});

Expand All @@ -154,7 +158,9 @@ describe("useDesktopData", () => {
);
render(<HookHarness activePage="sessions" />);
await waitFor(() => {
expect(screen.getByTestId("live-error")).toHaveTextContent("后端暂不可达,已进入退避重试");
expect(screen.getByTestId("live-error")).toHaveTextContent(
"The backend is currently unreachable. Backoff retry is active and local actions can continue.",
);
});
});

Expand Down Expand Up @@ -233,7 +239,9 @@ describe("useDesktopData", () => {
try {
render(<HookHarness activePage="sessions" />);
await waitFor(() => {
expect(screen.getByTestId("live-error")).toHaveTextContent("当前网络离线,已暂停实时拉取。恢复联网后将自动重试。");
expect(screen.getByTestId("live-error")).toHaveTextContent(
"The network is offline. Live polling is paused and will retry automatically when connectivity returns.",
);
});
} finally {
Object.defineProperty(window.navigator, "onLine", { configurable: true, value: originalOnLine });
Expand All @@ -260,7 +268,9 @@ describe("useDesktopData", () => {

render(<HookHarness activePage="sessions" />);
await waitFor(() => {
expect(screen.getByTestId("live-error")).toHaveTextContent("会话列表拉取失败:权限或认证异常,请确认登录状态。");
expect(screen.getByTestId("live-error")).toHaveTextContent(
"Failed to refresh the session list: authentication or permission check failed. Confirm your sign-in state.",
);
});
});

Expand Down Expand Up @@ -380,7 +390,7 @@ describe("useDesktopData", () => {
try {
render(<HookHarness activePage="gates" />);
await waitFor(() => {
expect(screen.getByTestId("live-error")).toHaveTextContent("策略告警拉取失败");
expect(screen.getByTestId("live-error")).toHaveTextContent("Failed to refresh policy alerts");
});
expect(consoleSpy).toHaveBeenCalled();
} finally {
Expand Down
17 changes: 9 additions & 8 deletions apps/desktop/src/lib/desktopUi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe("desktopUi seed timeline", () => {
),
);

fireEvent.click(screen.getByRole("button", { name: "查看完整 Diff" }));
fireEvent.click(screen.getByRole("button", { name: "View full diff" }));
expect(onViewDiff).toHaveBeenCalledWith("report-1");
});

Expand Down Expand Up @@ -108,8 +108,8 @@ describe("desktopUi seed timeline", () => {

render(createElement("div", null, renderChatEmbed(message as any, embed as any, chooseDecision)));

expect(screen.getByText("推荐")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "选择" }));
expect(screen.getByText("Recommended")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Choose" }));
expect(chooseDecision).toHaveBeenCalledWith("msg-decision", "decision-1", "fast");
});

Expand Down Expand Up @@ -157,10 +157,11 @@ describe("desktopUi seed timeline", () => {
)
);

expect(screen.getByText("任务:")).toBeInTheDocument();
expect(screen.getAllByText("进行中")).toHaveLength(2);
expect(screen.getByText("等待")).toBeInTheDocument();
expect(screen.getByText("完成")).toBeInTheDocument();
expect(screen.getByLabelText("警报卡片")).toHaveClass("is-critical");
expect(screen.getByText("Task:")).toBeInTheDocument();
expect(screen.getByText("进行中")).toBeInTheDocument();
expect(screen.getAllByText("In progress")).toHaveLength(1);
expect(screen.getByText("Waiting")).toBeInTheDocument();
expect(screen.getByText("Done")).toBeInTheDocument();
expect(screen.getByLabelText("Alert card")).toHaveClass("is-critical");
});
});
10 changes: 5 additions & 5 deletions apps/desktop/src/lib/uiError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ describe("uiError", () => {
});

it("maps network-style messages", () => {
expect(sanitizeUiError(new Error("Network timeout"), "加载失败")).toContain("未连接到本地服务");
expect(sanitizeUiError(new Error("fetch failed"), "加载失败")).toContain("未连接到本地服务");
expect(sanitizeUiError(new Error("Network timeout"), "Load failed")).toContain("unable to reach the local service");
expect(sanitizeUiError(new Error("fetch failed"), "Load failed")).toContain("unable to reach the local service");
});

it("maps auth-style messages", () => {
expect(sanitizeUiError(new Error("401 unauthorized"), "加载失败")).toContain("权限或认证异常");
expect(sanitizeUiError(new Error("token invalid"), "加载失败")).toContain("权限或认证异常");
expect(sanitizeUiError(new Error("401 unauthorized"), "Load failed")).toContain("authentication or permission check failed");
expect(sanitizeUiError(new Error("token invalid"), "Load failed")).toContain("authentication or permission check failed");
});

it("keeps generic fallback for unknown errors", () => {
expect(sanitizeUiError(new Error("boom"), "加载失败")).toBe("加载失败");
});

it("maps backend 5xx-style messages", () => {
expect(sanitizeUiError(new Error("API /path failed: 503"), "加载失败")).toContain("服务暂时不可用");
expect(sanitizeUiError(new Error("API /path failed: 503"), "Load failed")).toContain("service is temporarily unavailable");
});

it("extracts detail from unknown payload", () => {
Expand Down
Loading
Loading