diff --git a/README.md b/README.md
index 9126d8c2..6bba7668 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,30 @@
-# CortexPilot
+# OpenVibeCoding
-The command tower for AI engineering.
+The open command tower for AI engineering.
-Stop babysitting AI coding work. CortexPilot helps teams plan, delegate, track,
-resume, and prove long-running engineering work across Codex and Claude Code
-instead of juggling scattered chats, local scripts, and after-the-fact logs.
+Stop babysitting AI coding work.
+
+AI coding does not lack models. It lacks a command tower.
+
+OpenVibeCoding is the public shell for the CortexPilot repo and runtime
+compatibility layer. It helps teams plan / delegate / track / resume / prove
+long-running engineering work across Codex and Claude Code instead of
+juggling scattered chats, local scripts, and after-the-fact logs.
CortexPilot is a contract-first multi-agent orchestration repository.
+The underlying repository, runtime, package, and compatibility names remain
+`CortexPilot` where that internal surface is the truthful identifier.
+
The public story is intentionally narrower than the full monorepo:
- **See one proven workflow first**
- **Choose the right adoption path second**
- **Open MCP / API / builder / skills surfaces only after the real job is clear**
-Current public boundary: CortexPilot is a repo-backed AI engineering command
-tower, not a hosted product, and the shipped MCP surface remains **read-only**.
+Current public boundary: OpenVibeCoding is a repo-backed public shell over the
+CortexPilot runtime, not a hosted product, and the shipped MCP surface remains
+**read-only**.
Current lane order is deliberate:
@@ -31,7 +40,14 @@ Current lane order is deliberate:
The shortest truthful answer today is:
-> CortexPilot officially ships a public repo, a public Pages front door, a repo-local read-only MCP surface, a published PyPI package, a live Official MCP Registry entry, and a live ClawHub skill. The adoption-router skill is the secondary public lane. Local coding-agent starters and bundle examples remain companion/example materials, not the canonical public root. OpenHands/extensions and MCP.so external receipts exist, while hosted service, write-capable MCP, Docker distribution, and standalone npm releases remain deferred.
+> OpenVibeCoding currently ships through the public CortexPilot repo, a public
+> Pages front door, a repo-local read-only MCP surface, a published PyPI
+> package, a live Official MCP Registry entry, and a live ClawHub skill. The
+> adoption-router skill is the secondary public lane. Local coding-agent
+> starters and bundle examples remain companion/example materials, not the
+> canonical public root. OpenHands/extensions and MCP.so external receipts
+> exist, while hosted service, write-capable MCP, Docker distribution, and
+> standalone npm releases remain deferred.
Use these buckets:
@@ -99,11 +115,11 @@ release, new task templates, and storefront updates.
If you need contributor setup instead of product evaluation, jump to the
[30-minute onboarding guide](docs/runbooks/onboarding-30min.md).
-## Why CortexPilot Exists
+## Why OpenVibeCoding Exists
-Most agent demos stop at "the model replied." CortexPilot is built for the next
-question: **can we inspect what happened, review what changed, classify the
-workflow case, and rerun it without guessing?**
+Most agent demos stop at "the model replied." OpenVibeCoding is built for the
+next question: **can we inspect what happened, review what changed, classify
+the workflow case, and rerun it without guessing?**
The deeper product claim is straightforward:
@@ -119,7 +135,7 @@ The engineering philosophy underneath that loop is equally explicit:
- **Context Engineering**: keep the right material in the right head, and treat explicit handoff as a fallback rather than the default loop.
- **Harness Engineering**: move work through contracts, runtime bindings, approvals, and proof surfaces so the system can keep operating safely.
-This repository combines:
+The CortexPilot runtime underneath that public shell combines:
- **Command Tower**: one operator surface for governed AI work, live run visibility, queue posture, and L0-style oversight
- **Workflow Cases**: one stable operating record that ties request, verdict, proof, and linked runs together
@@ -189,7 +205,7 @@ If the first success path fails, go here next:
## The First Loop
-The clearest way to understand CortexPilot is:
+The clearest way to understand OpenVibeCoding is:
1. **PM**: describe the task and acceptance target
2. **Workflow Case**: confirm the case identity, queue state, and operating verdict
@@ -214,9 +230,10 @@ repository should be judged on.
`Switchyard /v1/runtime/invoke` for intake/operator-style chat paths, but
MCP tool execution still needs a tool-capable provider path and therefore
fails closed instead of pretending Switchyard already has tool parity
-- CortexPilot is still **not** a hosted operator service; `cortexpilot.ai`
- should be treated as a marketing/holding domain until the public contract,
- support boundary, and live surface materially change
+- the CortexPilot runtime under OpenVibeCoding is still **not** a hosted
+ operator service; `cortexpilot.ai` should be treated as a marketing/holding
+ domain until the public contract, support boundary, and live surface
+ materially change
## Public CI Safety Model
@@ -322,7 +339,7 @@ release-proven until they have their own healthy proof and benchmark artifacts.
Use these names as ecosystem anchors, not as co-brands or partnership claims.
-- **Codex**: primary workflow audience; CortexPilot is built for governed
+- **Codex**: primary workflow audience; OpenVibeCoding is built for governed
Codex-style coding runs that need cases, approvals, and replayable proof.
- **Claude Code**: primary workflow audience alongside Codex; the same
Command Tower / Workflow Case / Proof & Replay spine applies.
@@ -340,7 +357,7 @@ Use these names as ecosystem anchors, not as co-brands or partnership claims.
## Official Ecosystem Anchors
When a team asks "what is real on their side?", start from the native surfaces
-below before you explain where CortexPilot fits:
+below before you explain where OpenVibeCoding fits:
- **Codex**:
- repo: [openai/codex](https://github.com/openai/codex)
@@ -359,21 +376,21 @@ below before you explain where CortexPilot fits:
- skills docs: [docs.openclaw.ai/tools/skills](https://docs.openclaw.ai/tools/skills)
- registry/catalog: [openclaw/clawhub](https://github.com/openclaw/clawhub)
-These anchors matter because CortexPilot should fit around the real ecosystem
+These anchors matter because OpenVibeCoding should fit around the real ecosystem
surfaces that already exist:
- **Codex**: Codex now has real plugin surfaces of its own, including local
- marketplace installs and a curated official directory. CortexPilot should sit
+ marketplace installs and a curated official directory. OpenVibeCoding should sit
around Codex workflows with command tower, proof, replay, read-only MCP, and
repo-owned skills or local bundle examples until a real published listing
exists.
- **Claude Code**: Claude Code's current native surfaces include plugins, MCP,
- hooks, subagents, and project configuration. CortexPilot should wrap those
+ hooks, subagents, and project configuration. OpenVibeCoding should wrap those
governed workflows with command tower, proof, replay, read-only MCP, and
- repo-owned starter kits rather than pretending a published CortexPilot
+ repo-owned starter kits rather than pretending a published OpenVibeCoding
marketplace listing already exists.
- **OpenClaw**: adjacent integration layer with real skills and plugin/catalog
- surfaces on its side, while CortexPilot stays on the review/proof/read-only
+ surfaces on its side, while OpenVibeCoding stays on the review/proof/read-only
integration side unless a mapped native path is explicitly shipped and
tested.
@@ -387,13 +404,13 @@ The strongest public loop is now:
3. Reuse the Workflow Case as a **share-ready recap asset** instead of keeping
it trapped inside a single operator page.
-That turns CortexPilot from “a repo you can run” into “a repo you can show,
+That turns OpenVibeCoding from “a repo you can run” into “a repo you can show,
review, and hand off.”
## Builder Entry Points
These are the current public-facing entry points for teams that want to build
-around CortexPilot without pretending a full SDK platform already exists:
+around OpenVibeCoding without pretending a full SDK platform already exists:
- [packages/frontend-api-client/README.md](packages/frontend-api-client/README.md): thin JavaScript/TypeScript client entry points for dashboard, desktop, and web surfaces, including the repo-owned `createControlPlaneStarter(...)` bootstrap path for overview + agents + contracts + role-config integration.
- [packages/frontend-api-contract/docs/README.md](packages/frontend-api-contract/docs/README.md): human-readable contract package guide for generated frontend-safe route/query/type imports.
@@ -413,7 +430,7 @@ If your team needs starter assets instead of only wording, open:
## Shortest Cross-Ecosystem Adoption Order
-If you are integrating CortexPilot into a coding-agent workflow, the shortest
+If you are integrating OpenVibeCoding into a coding-agent workflow, the shortest
truthful order is:
1. Confirm the native ecosystem surface first:
@@ -421,8 +438,8 @@ truthful order is:
- Claude Code overview / MCP
- OpenClaw repo / skills / ClawHub
2. Use the public [compatibility matrix](https://xiaojiou176-open.github.io/CortexPilot-public/compatibility/)
- to choose the right CortexPilot entrypoint.
-3. Pick the first CortexPilot lane based on the job:
+ to choose the right OpenVibeCoding entrypoint.
+3. Pick the first OpenVibeCoding lane based on the job:
- [read-only MCP](https://xiaojiou176-open.github.io/CortexPilot-public/mcp/)
for protocol inspection
- [skills quickstart](https://xiaojiou176-open.github.io/CortexPilot-public/skills/)
@@ -445,7 +462,7 @@ truthful order is:
## Best Fit
-CortexPilot is a strong fit if you are building or evaluating:
+OpenVibeCoding is a strong fit if you are building or evaluating:
- agent workflows that need **reviewable evidence**
- orchestration systems that need **replay / re-exec**
@@ -454,7 +471,7 @@ CortexPilot is a strong fit if you are building or evaluating:
## Not A Fit
-CortexPilot is not the right choice if you want:
+OpenVibeCoding is not the right choice if you want:
- a polished hosted SaaS product
- write-capable agent control-plane mutations through MCP today
@@ -488,7 +505,7 @@ The current stage freeze keeps two high-risk directions explicitly constrained:
- **Hosted operator surface** remains **No-Go**.
- `cortexpilot.ai` is still a weak marketing/holding domain, not a production
front door.
-- The current public contract still describes CortexPilot as source code plus
+- The current public contract still describes OpenVibeCoding as source code plus
operator/demo surfaces, not as a hosted service.
- Reopen hosted only if the public boundary, support contract, privacy/security
wording, and live front door materially change together.
diff --git a/apps/dashboard/README.md b/apps/dashboard/README.md
index ae8ea3d4..7124c252 100644
--- a/apps/dashboard/README.md
+++ b/apps/dashboard/README.md
@@ -42,6 +42,12 @@ repository is already a finished consumer product.
- PM workspace: registry-driven task-pack selection plus `execution_plan_report`
preview before execution starts.
+- Planner desk: a first-class planner-facing triage route that pulls
+ `planning_wave_plan`, `planning_worker_prompt_contracts`,
+ `planning_unblock_tasks`, and `completion_governance_report` into one
+ planning control desk, now with inline queue / dispatch controls so planners
+ can act on the next contract instead of only reading artifacts and clicking
+ away to other desks.
- Agents: the first-screen role catalog now also hosts a repo-owned role
configuration desk for previewing and saving future compiled defaults
(`system_prompt_ref`, bundle refs, and role-level runtime binding) while
diff --git a/apps/dashboard/app/agents/page.tsx b/apps/dashboard/app/agents/page.tsx
index f2e051c6..4b20b4d5 100644
--- a/apps/dashboard/app/agents/page.tsx
+++ b/apps/dashboard/app/agents/page.tsx
@@ -419,78 +419,6 @@ export default async function AgentsPage({ searchParams }: AgentsPageProps) {
{agentsPageCopy.metricSublines.schedulerHint(lockedAgentCount)}
-
-
-
-
-
Role catalog (read-only first screen)
-
{agentsPageCopy.roleCatalog.title}
-
{agentsPageCopy.roleCatalog.subtitle}
-
-
-
- {agentsPageCopy.roleCatalog.fullList}
-
-
-
-
- {payloadWarning ? (
- {agentsPageCopy.roleCatalog.registryUnavailable}
- ) : roleCatalog.length === 0 ? (
- {agentsPageCopy.roleCatalog.noMatches}
- ) : (
-
-
-
- {agentsPageCopy.roleCatalog.headers.role}
- {agentsPageCopy.roleCatalog.headers.skillsBundle}
- {agentsPageCopy.roleCatalog.headers.mcpBundle}
- {agentsPageCopy.roleCatalog.headers.runtimeBinding}
- {agentsPageCopy.roleCatalog.headers.executionAuthority}
- {agentsPageCopy.roleCatalog.headers.registeredSeats}
-
-
-
- {roleCatalog.map((roleEntry) => (
-
-
-
- {roleEntry.role}
- {roleEntry.purpose || agentsPageCopy.roleCatalog.noRolePurpose}
-
-
-
-
- {formatBindingReadModelLabel(roleEntry.role_binding_read_model?.skills_bundle_ref)}
-
-
-
-
- {formatBindingReadModelLabel(roleEntry.role_binding_read_model?.mcp_bundle_ref)}
-
-
- {formatRoleBindingRuntimeSummary(roleEntry.role_binding_read_model)}
-
-
- {roleEntry.role_binding_read_model.execution_authority}
- {agentsPageCopy.roleCatalog.readOnlyMirror}
-
-
-
-
- {roleEntry.registered_agent_count}
-
- {roleEntry.locked_agent_count} {agentsPageCopy.roleCatalog.lockedSuffix}
-
-
-
-
- ))}
-
-
- )}
-
-
@@ -530,7 +458,6 @@ export default async function AgentsPage({ searchParams }: AgentsPageProps) {
-
{/* ── Active State Machine ── */}
@@ -788,6 +715,84 @@ export default async function AgentsPage({ searchParams }: AgentsPageProps) {
+
+
+
+
+
+ {agentsPageCopy.roleCatalog.title}
+
+
+
+
{agentsPageCopy.roleCatalog.subtitle}
+
+
+
+ {agentsPageCopy.roleCatalog.fullList}
+
+
+
+
+ {payloadWarning ? (
+
{agentsPageCopy.roleCatalog.registryUnavailable}
+ ) : roleCatalog.length === 0 ? (
+
{agentsPageCopy.roleCatalog.noMatches}
+ ) : (
+
+
+
+ {agentsPageCopy.roleCatalog.headers.role}
+ {agentsPageCopy.roleCatalog.headers.skillsBundle}
+ {agentsPageCopy.roleCatalog.headers.mcpBundle}
+ {agentsPageCopy.roleCatalog.headers.runtimeBinding}
+ {agentsPageCopy.roleCatalog.headers.executionAuthority}
+ {agentsPageCopy.roleCatalog.headers.registeredSeats}
+
+
+
+ {roleCatalog.map((roleEntry) => (
+
+
+
+ {roleEntry.role}
+ {roleEntry.purpose || agentsPageCopy.roleCatalog.noRolePurpose}
+
+
+
+
+ {formatBindingReadModelLabel(roleEntry.role_binding_read_model?.skills_bundle_ref)}
+
+
+
+
+ {formatBindingReadModelLabel(roleEntry.role_binding_read_model?.mcp_bundle_ref)}
+
+
+ {formatRoleBindingRuntimeSummary(roleEntry.role_binding_read_model)}
+
+
+ {roleEntry.role_binding_read_model.execution_authority}
+ {agentsPageCopy.roleCatalog.readOnlyMirror}
+
+
+
+
+ {roleEntry.registered_agent_count}
+
+ {roleEntry.locked_agent_count} {agentsPageCopy.roleCatalog.lockedSuffix}
+
+
+
+
+ ))}
+
+
+ )}
+
+
+
+
+
diff --git a/apps/dashboard/app/command-tower/page.tsx b/apps/dashboard/app/command-tower/page.tsx
index 63fbea0b..33acb8b7 100644
--- a/apps/dashboard/app/command-tower/page.tsx
+++ b/apps/dashboard/app/command-tower/page.tsx
@@ -12,9 +12,9 @@ import type { CommandTowerOverviewPayload, PmSessionSummary } from "../../lib/ty
import type { UiLocale } from "@cortexpilot/frontend-shared/uiCopy";
export const metadata: Metadata = {
- title: "Command Tower | CortexPilot",
+ title: "Command Tower | OpenVibeCoding",
description:
- "Monitor live operator visibility, PM sessions, and governed coding-agent work from the CortexPilot Command Tower with linked Workflow Cases.",
+ "Monitor live operator visibility, linked Workflow Cases, blockers, and next operator actions from the OpenVibeCoding command tower cockpit.",
};
async function CommandTowerHomeSection({ locale }: { locale: UiLocale }) {
diff --git a/apps/dashboard/app/contracts/page.tsx b/apps/dashboard/app/contracts/page.tsx
index 58d15678..faf37b1d 100644
--- a/apps/dashboard/app/contracts/page.tsx
+++ b/apps/dashboard/app/contracts/page.tsx
@@ -1,4 +1,5 @@
import { cookies } from "next/headers";
+import Link from "next/link";
import { getUiCopy } from "@cortexpilot/frontend-shared/uiCopy";
import { normalizeUiLocale, UI_LOCALE_STORAGE_KEY } from "@cortexpilot/frontend-shared/uiLocale";
import { Badge } from "../../components/ui/badge";
@@ -39,6 +40,67 @@ export default async function ContractsPage({
}) {
const locale = await resolveDashboardLocale();
const contractsPageCopy = getUiCopy(locale).dashboard.contractsPage;
+ const shellCopy =
+ locale === "zh-CN"
+ ? {
+ actionTitle: "先做哪一步",
+ actionSubtitle: "先确认哪份 contract 真能继续,哪份应该转去 Proof、Role 或 Workflow 再处理。",
+ commandTowerAction: "打开 Command Tower",
+ workflowAction: "打开 Workflow Cases",
+ proofAction: "打开 Proof & Replay",
+ riskTitle: "需要先处理的 contract",
+ riskHint: "缺执行权、缺关联 run、或缺验收测试的 contract 优先处理。",
+ proofLinkedTitle: "已绑定 Proof & Replay",
+ proofLinkedHint: "已经能直接回到 run / proof room 的 contract 数量。",
+ authorityTitle: "已发布执行权",
+ authorityHint: "execution authority 已经明确对外可读的 contract 数量。",
+ nextActionTitle: "下一步",
+ openProofRoom: "打开关联证明室",
+ inspectRoleDesk: "查看角色桌",
+ inspectWorkflowDesk: "查看 Workflow desk",
+ nextActionHintWithRun: "先回到 run / proof,确认这份 contract 当前是否真能继续。",
+ nextActionHintWithoutRun:
+ "这份 contract 还没有稳定的 run 下钻口,先回到 workflow 或 role desk 看 owner、authority 和 blocker。",
+ }
+ : {
+ actionTitle: "Start with the next action",
+ actionSubtitle:
+ "Confirm which contracts can continue, which need proof review, and which should send you back to Workflow Cases or the role desk.",
+ commandTowerAction: "Open Command Tower",
+ workflowAction: "Open Workflow Cases",
+ proofAction: "Open Proof & Replay",
+ riskTitle: "Contracts needing triage",
+ riskHint: "Prioritise contracts missing execution authority, linked runs, or acceptance tests.",
+ proofLinkedTitle: "Contracts linked to proof",
+ proofLinkedHint: "Contracts that already point straight back to a run / proof room.",
+ authorityTitle: "Published execution authority",
+ authorityHint: "Contracts whose execution authority is already explicit on the first screen.",
+ nextActionTitle: "Next action",
+ triageDeskTitle: "Contract triage queue",
+ triageDeskSubtitle:
+ "Start from the contract that is missing authority, run linkage, or acceptance evidence, then open proof or the role desk from there.",
+ triageHeaders: {
+ contract: "Contract",
+ role: "Role",
+ blocker: "Next blocker",
+ action: "Next action",
+ },
+ blockerReady: "Ready for proof review",
+ blockerMissingAuthority: "Missing execution authority",
+ blockerMissingRun: "Missing linked run",
+ blockerMissingTests: "Missing acceptance tests",
+ openProofRoom: "Open related proof room",
+ inspectRoleDesk: "Inspect role desk",
+ inspectWorkflowDesk: "Inspect workflow desk",
+ nextActionHintWithRun:
+ "Return to the related run first, then decide whether this contract is truly ready to continue.",
+ nextActionHintWithoutRun:
+ "This contract still needs workflow or role triage before you trust it to continue.",
+ secondaryDetailsSummary: "Execution details",
+ inspectionDeckTitle: "Inspection archive",
+ inspectionDeckSubtitle:
+ "Keep the first screen for triage and next action. Open the archive only when you need payload, permissions, or runtime binding detail.",
+ };
const { data: contracts, warning } = await safeLoad(fetchContracts, [] as ContractRecord[], "Contract list");
const resolvedSearchParams = (await searchParams) || {};
const query = String(resolvedSearchParams.q || "").trim().toLowerCase();
@@ -53,6 +115,12 @@ export default async function ContractsPage({
});
const visibleContracts = filteredContracts.slice(0, limit);
const canApplyFilter = Boolean(query);
+ const contractsNeedingTriage = filteredContracts.filter((contract) => {
+ const acceptanceTests = Array.isArray(contract.acceptance_tests) ? contract.acceptance_tests : [];
+ return !contract.execution_authority || !contract.run_id || acceptanceTests.length === 0;
+ }).length;
+ const contractsWithRuns = filteredContracts.filter((contract) => Boolean(contract.run_id)).length;
+ const contractsWithAuthority = filteredContracts.filter((contract) => Boolean(contract.execution_authority)).length;
return (
@@ -63,6 +131,42 @@ export default async function ContractsPage({
{contractsPageCopy.countsBadge(contracts.length)}
+
+
+
{shellCopy.actionTitle}
+
{shellCopy.actionSubtitle}
+
+
+
+ {shellCopy.commandTowerAction}
+
+
+ {shellCopy.workflowAction}
+
+
+ {shellCopy.proofAction}
+
+
+
+
+
+ {shellCopy.riskTitle}
+ 0 ? "metric-value--warning" : "metric-value--success"}`}>
+ {contractsNeedingTriage}
+
+ {shellCopy.riskHint}
+
+
+ {shellCopy.proofLinkedTitle}
+ {contractsWithRuns}
+ {shellCopy.proofLinkedHint}
+
+
+ {shellCopy.authorityTitle}
+ {contractsWithAuthority}
+ {shellCopy.authorityHint}
+
+
@@ -87,6 +191,65 @@ export default async function ContractsPage({
) : null}
+ {visibleContracts.length > 0 ? (
+
+
+
+
{shellCopy.triageDeskTitle}
+
{shellCopy.triageDeskSubtitle}
+
+
+
+
+
+ {shellCopy.triageHeaders.contract}
+ {shellCopy.triageHeaders.role}
+ {shellCopy.triageHeaders.blocker}
+ {shellCopy.triageHeaders.action}
+
+
+
+ {visibleContracts.map((contract, index) => {
+ const acceptanceTests = Array.isArray(contract.acceptance_tests) ? contract.acceptance_tests : [];
+ const blockers = [
+ !contract.execution_authority ? shellCopy.blockerMissingAuthority : "",
+ !contract.run_id ? shellCopy.blockerMissingRun : "",
+ acceptanceTests.length === 0 ? shellCopy.blockerMissingTests : "",
+ ].filter(Boolean);
+ const proofHref = contract.run_id ? `/runs/${contract.run_id}` : "/runs";
+ const roleDeskHref = contract.assigned_role
+ ? `/agents?role=${encodeURIComponent(String(contract.assigned_role))}`
+ : "/agents";
+ return (
+
+
+
+ {contract.task_id || contract.path || contractsPageCopy.fallbackValues.unknownContract}
+
+
+ {contract.assigned_role || contractsPageCopy.fallbackValues.notAssigned}
+
+ 0 ? "warning" : "success"}>
+ {blockers.length > 0 ? blockers.join(" / ") : shellCopy.blockerReady}
+
+
+
+
+
+ {shellCopy.openProofRoom}
+
+
+ {shellCopy.inspectRoleDesk}
+
+
+
+
+ );
+ })}
+
+
+
+ ) : null}
{filteredContracts.length === 0 ? (
@@ -95,7 +258,12 @@ export default async function ContractsPage({
) : (
-
+
+
+ {shellCopy.inspectionDeckTitle}
+
+
{shellCopy.inspectionDeckSubtitle}
+
{visibleContracts.map((contract) => {
const payload = contract.payload || {};
const key = contract.path || contract.task_id || contract.run_id || JSON.stringify(payload).slice(0, 40);
@@ -104,6 +272,10 @@ export default async function ContractsPage({
const toolPermissions = contract.tool_permissions || {};
const permissionSummary = summarizeToolPermissions(toolPermissions);
const roleBinding = contract.role_binding_read_model;
+ const proofHref = contract.run_id ? `/runs/${contract.run_id}` : "/runs";
+ const roleDeskHref = contract.assigned_role
+ ? `/agents?role=${encodeURIComponent(String(contract.assigned_role))}`
+ : "/agents";
return (
@@ -142,44 +314,6 @@ export default async function ContractsPage({
)}
-
- {contractsPageCopy.fieldLabels.skillsBundle}
-
- {roleBinding
- ? formatBindingReadModelLabel(roleBinding.skills_bundle_ref)
- : contractsPageCopy.fallbackValues.notDerived}
-
-
-
- {contractsPageCopy.fieldLabels.mcpBundle}
-
- {roleBinding
- ? formatBindingReadModelLabel(roleBinding.mcp_bundle_ref)
- : contractsPageCopy.fallbackValues.notDerived}
-
-
-
- {contractsPageCopy.fieldLabels.runtimeBinding}
-
- {roleBinding
- ? formatRoleBindingRuntimeSummary(roleBinding)
- : contractsPageCopy.fallbackValues.notDerived}
-
-
-
- {contractsPageCopy.fieldLabels.runtimeCapability}
-
- {roleBinding?.runtime_binding?.capability?.lane || contractsPageCopy.fallbackValues.notDerived}
-
-
-
- {contractsPageCopy.fieldLabels.toolExecution}
-
- {roleBinding
- ? formatRoleBindingRuntimeCapabilitySummary(roleBinding)
- : contractsPageCopy.fallbackValues.notDerived}
-
-
{contractsPageCopy.fieldLabels.allowedPaths}
@@ -208,32 +342,92 @@ export default async function ContractsPage({
)}
-
- {contractsPageCopy.fieldLabels.toolPermissions}
-
- {permissionSummary.length > 0 ? (
-
- {permissionSummary.map((entry) => (
- {entry}
- ))}
-
- ) : (
- {contractsPageCopy.fallbackValues.defaultPermissions}
- )}
-
-
+
+
+ {shellCopy.openProofRoom}
+
+
+ {shellCopy.inspectRoleDesk}
+
+ {!contract.run_id ? (
+
+ {shellCopy.inspectWorkflowDesk}
+
+ ) : null}
+
+
+ {contract.run_id ? shellCopy.nextActionHintWithRun : shellCopy.nextActionHintWithoutRun}
+
- {contractsPageCopy.fullJsonSummary}
+ {shellCopy.secondaryDetailsSummary}
+
+
+ {contractsPageCopy.fieldLabels.skillsBundle}
+
+ {roleBinding
+ ? formatBindingReadModelLabel(roleBinding.skills_bundle_ref)
+ : contractsPageCopy.fallbackValues.notDerived}
+
+
+
+ {contractsPageCopy.fieldLabels.mcpBundle}
+
+ {roleBinding
+ ? formatBindingReadModelLabel(roleBinding.mcp_bundle_ref)
+ : contractsPageCopy.fallbackValues.notDerived}
+
+
+
+ {contractsPageCopy.fieldLabels.runtimeBinding}
+
+ {roleBinding
+ ? formatRoleBindingRuntimeSummary(roleBinding)
+ : contractsPageCopy.fallbackValues.notDerived}
+
+
+
+ {contractsPageCopy.fieldLabels.runtimeCapability}
+
+ {roleBinding?.runtime_binding?.capability?.lane || contractsPageCopy.fallbackValues.notDerived}
+
+
+
+ {contractsPageCopy.fieldLabels.toolExecution}
+
+ {roleBinding
+ ? formatRoleBindingRuntimeCapabilitySummary(roleBinding)
+ : contractsPageCopy.fallbackValues.notDerived}
+
+
+
+ {contractsPageCopy.fieldLabels.toolPermissions}
+
+ {permissionSummary.length > 0 ? (
+
+ {permissionSummary.map((entry) => (
+ {entry}
+ ))}
+
+ ) : (
+ {contractsPageCopy.fallbackValues.defaultPermissions}
+ )}
+
+
+
+
{contractsPageCopy.fullJsonSummary}
{JSON.stringify(payload || { raw_preview: contract.raw_preview }, null, 2)}
);
})}
-
+
+
+
+
)}
{filteredContracts.length > limit ? (
diff --git a/apps/dashboard/app/globals.tokens.css b/apps/dashboard/app/globals.tokens.css
index 640e23a0..7d1c9183 100644
--- a/apps/dashboard/app/globals.tokens.css
+++ b/apps/dashboard/app/globals.tokens.css
@@ -2,77 +2,77 @@
@import "./globals.responsive.css";
/* =========================================
- CortexPilot Command Tower Command Center — Design Tokens
- Light-first, clean white aesthetic
+ OpenVibeCoding Command Tower — Design Tokens
+ Dark-neutral command deck baseline
========================================= */
:root {
- color-scheme: light;
+ color-scheme: dark;
/* ── Core Palette ── */
- --color-bg: #f9fafb;
- --color-bg-elevated: #ffffff;
- --color-bg-surface: #f3f4f6;
- --color-surface-subtle: #f3f4f6;
- --color-surface-hover: #e5e7eb;
-
- --color-text-primary: #111827;
- --color-text-secondary: #4b5563;
- --color-text-muted: #4b5563;
- --color-text-tertiary: #52525b;
-
- --color-primary: #2563eb;
- --color-primary-hover: #1d4ed8;
- --color-primary-soft: rgba(37, 99, 235, 0.08);
- --color-primary-muted: rgba(37, 99, 235, 0.04);
-
- --color-border: #e5e7eb;
- --color-border-strong: #d1d5db;
- --color-border-subtle: #f3f4f6;
- --color-focus-ring: #2563eb;
+ --color-bg: #0b1220;
+ --color-bg-elevated: #111a2e;
+ --color-bg-surface: #17233b;
+ --color-surface-subtle: #142038;
+ --color-surface-hover: #1b2a45;
+
+ --color-text-primary: #e8eef8;
+ --color-text-secondary: #b7c6db;
+ --color-text-muted: #9fb0c8;
+ --color-text-tertiary: #8396b3;
+
+ --color-primary: #1fb981;
+ --color-primary-hover: #17a16f;
+ --color-primary-soft: rgba(31, 185, 129, 0.12);
+ --color-primary-muted: rgba(31, 185, 129, 0.06);
+
+ --color-border: rgba(159, 176, 200, 0.16);
+ --color-border-strong: rgba(159, 176, 200, 0.28);
+ --color-border-subtle: rgba(159, 176, 200, 0.1);
+ --color-focus-ring: #1fb981;
/* ── Semantic Status ── */
- --color-success: #059669;
- --color-success-bg: rgba(5, 150, 105, 0.06);
- --color-success-ink: #065f46;
- --color-warning: #d97706;
- --color-warning-bg: rgba(217, 119, 6, 0.06);
- --color-warning-ink: #92400e;
- --color-danger: #dc2626;
- --color-danger-bg: rgba(220, 38, 38, 0.05);
- --color-danger-ink: #b91c1c;
- --color-info-bg: rgba(37, 99, 235, 0.05);
- --color-bg-glass: rgba(249, 250, 251, 0.85);
- --color-bg-overlay: rgba(0, 0, 0, 0.2);
- --color-text-on-primary: #ffffff;
- --color-border-success-soft: rgba(5, 150, 105, 0.2);
- --color-border-primary-soft: rgba(37, 99, 235, 0.2);
- --color-border-danger-soft: rgba(220, 38, 38, 0.2);
- --color-border-warning-soft: rgba(217, 119, 6, 0.2);
- --color-border-danger-mid: rgba(239, 68, 68, 0.3);
- --color-border-warning-mid: rgba(234, 179, 8, 0.3);
- --color-border-info-mid: rgba(59, 130, 246, 0.3);
- --color-border-primary-mid: rgba(59, 130, 246, 0.4);
- --color-border-success-mid: rgba(34, 197, 94, 0.3);
- --color-border-success-strong: rgba(34, 197, 94, 0.4);
- --color-gradient-primary-soft: linear-gradient(160deg, rgba(37, 99, 235, 0.015) 0%, transparent 50%);
+ --color-success: #1fb981;
+ --color-success-bg: rgba(31, 185, 129, 0.12);
+ --color-success-ink: #7fe0b7;
+ --color-warning: #e59a2f;
+ --color-warning-bg: rgba(229, 154, 47, 0.12);
+ --color-warning-ink: #f2c27d;
+ --color-danger: #d95c5c;
+ --color-danger-bg: rgba(217, 92, 92, 0.12);
+ --color-danger-ink: #f0aaaa;
+ --color-info-bg: rgba(31, 185, 129, 0.08);
+ --color-bg-glass: rgba(17, 26, 46, 0.84);
+ --color-bg-overlay: rgba(8, 12, 22, 0.5);
+ --color-text-on-primary: #08131c;
+ --color-border-success-soft: rgba(31, 185, 129, 0.28);
+ --color-border-primary-soft: rgba(31, 185, 129, 0.24);
+ --color-border-danger-soft: rgba(217, 92, 92, 0.24);
+ --color-border-warning-soft: rgba(229, 154, 47, 0.24);
+ --color-border-danger-mid: rgba(217, 92, 92, 0.34);
+ --color-border-warning-mid: rgba(229, 154, 47, 0.34);
+ --color-border-info-mid: rgba(31, 185, 129, 0.3);
+ --color-border-primary-mid: rgba(31, 185, 129, 0.4);
+ --color-border-success-mid: rgba(31, 185, 129, 0.4);
+ --color-border-success-strong: rgba(31, 185, 129, 0.5);
+ --color-gradient-primary-soft: linear-gradient(160deg, rgba(31, 185, 129, 0.08) 0%, transparent 55%);
--color-gradient-dark-overlay: linear-gradient(160deg, rgba(255, 255, 255, 0.04) 0%, transparent 50%);
- --color-app-bg-radial-indigo: color-mix(in srgb, var(--color-primary) 16%, transparent);
- --color-app-bg-radial-cyan: color-mix(in srgb, var(--color-success) 14%, transparent);
- --color-app-bg-base-start: color-mix(in srgb, var(--color-bg-elevated) 88%, var(--color-bg));
- --color-app-bg-base-mid: color-mix(in srgb, var(--color-bg-surface) 56%, var(--color-bg-elevated));
- --color-app-bg-base-end: var(--color-bg-surface);
- --color-app-grid-line: color-mix(in srgb, var(--color-border) 18%, transparent);
- --shadow-pulse-start: 0 0 0 0 rgba(59, 130, 246, 0.25);
- --shadow-pulse-mid: 0 0 0 8px rgba(59, 130, 246, 0);
- --shadow-pulse-end: 0 0 0 0 rgba(59, 130, 246, 0);
+ --color-app-bg-radial-indigo: color-mix(in srgb, var(--color-primary) 18%, transparent);
+ --color-app-bg-radial-cyan: color-mix(in srgb, var(--color-warning) 10%, transparent);
+ --color-app-bg-base-start: color-mix(in srgb, var(--color-bg-elevated) 94%, var(--color-bg));
+ --color-app-bg-base-mid: color-mix(in srgb, var(--color-bg-surface) 64%, var(--color-bg-elevated));
+ --color-app-bg-base-end: var(--color-bg);
+ --color-app-grid-line: color-mix(in srgb, var(--color-border) 55%, transparent);
+ --shadow-pulse-start: 0 0 0 0 rgba(31, 185, 129, 0.25);
+ --shadow-pulse-mid: 0 0 0 8px rgba(31, 185, 129, 0);
+ --shadow-pulse-end: 0 0 0 0 rgba(31, 185, 129, 0);
/* ── Shadows ── */
- --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
- --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
- --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.04);
- --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
- --shadow-glow-primary: 0 0 20px rgba(37, 99, 235, 0.08);
+ --shadow-sm: 0 1px 2px rgba(4, 7, 13, 0.38), 0 0 0 1px rgba(232, 238, 248, 0.03);
+ --shadow-md: 0 6px 18px rgba(4, 7, 13, 0.42), 0 0 0 1px rgba(232, 238, 248, 0.04);
+ --shadow-lg: 0 14px 36px rgba(4, 7, 13, 0.48), 0 0 0 1px rgba(232, 238, 248, 0.05);
+ --shadow-xl: 0 24px 54px rgba(4, 7, 13, 0.55);
+ --shadow-glow-primary: 0 0 18px rgba(31, 185, 129, 0.14);
/* ── Radius ── */
--radius-xs: 4px;
@@ -167,61 +167,6 @@
--danger-bg: var(--color-danger-bg);
}
-/* ── Dark Mode Override ── */
-@media (prefers-color-scheme: dark) {
- :root {
- color-scheme: dark;
- --color-bg: #09090b;
- --color-bg-elevated: #0f0f11;
- --color-bg-surface: #161618;
- --color-surface-subtle: #1c1c1f;
- --color-surface-hover: #232326;
- --color-text-primary: #fafafa;
- --color-text-secondary: #a0a0ab;
- --color-text-muted: #8b8b95;
- --color-text-tertiary: #a1a1aa;
- --color-primary: #3b82f6;
- --color-primary-hover: #2563eb;
- --color-primary-soft: rgba(59, 130, 246, 0.15);
- --color-primary-muted: rgba(59, 130, 246, 0.08);
- --color-border: #27272a;
- --color-border-strong: #3f3f46;
- --color-border-subtle: #1e1e21;
- --color-focus-ring: #3b82f6;
- --color-success: #22c55e;
- --color-success-bg: rgba(34, 197, 94, 0.10);
- --color-success-ink: #4ade80;
- --color-warning: #eab308;
- --color-warning-bg: rgba(234, 179, 8, 0.10);
- --color-warning-ink: #facc15;
- --color-danger: #ef4444;
- --color-danger-bg: rgba(239, 68, 68, 0.10);
- --color-danger-ink: #f87171;
- --color-info-bg: rgba(59, 130, 246, 0.10);
- --color-app-bg-radial-indigo: color-mix(in srgb, var(--color-primary) 22%, transparent);
- --color-app-bg-radial-cyan: color-mix(in srgb, var(--color-success) 18%, transparent);
- --color-app-bg-base-start: color-mix(in srgb, var(--color-bg-elevated) 92%, var(--color-bg));
- --color-app-bg-base-mid: color-mix(in srgb, var(--color-bg-surface) 62%, var(--color-bg-elevated));
- --color-app-bg-base-end: var(--color-bg-surface);
- --color-app-grid-line: color-mix(in srgb, var(--color-border) 30%, transparent);
- --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.03);
- --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04);
- --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);
- --shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.6);
- --shadow-glow-primary: 0 0 24px rgba(59, 130, 246, 0.15);
- }
-
- .topbar {
- background: var(--color-bg-elevated);
- backdrop-filter: blur(12px) saturate(180%);
- -webkit-backdrop-filter: blur(12px) saturate(180%);
- }
-
- .metric-card::before {
- background: var(--color-gradient-dark-overlay);
- }
-}
-
/* =========================================
Global Reset & Base
========================================= */
@@ -236,7 +181,7 @@ html,
body {
margin: 0;
padding: 0;
- font-family: var(--font-ui, "Inter"), -apple-system, "PingFang SC", "Microsoft YaHei",
+ font-family: var(--font-ui, var(--font-manrope, "Manrope")), -apple-system, "PingFang SC", "Microsoft YaHei",
"Noto Sans CJK SC", "Helvetica Neue", sans-serif;
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
font-optical-sizing: auto;
diff --git a/apps/dashboard/app/layout.tsx b/apps/dashboard/app/layout.tsx
index 9350e761..4c866fe8 100644
--- a/apps/dashboard/app/layout.tsx
+++ b/apps/dashboard/app/layout.tsx
@@ -4,9 +4,9 @@ import WebVitalsBridge from "../components/WebVitalsBridge";
import "./globals.css";
export const metadata = {
- title: "CortexPilot | The command tower for AI engineering",
+ title: "OpenVibeCoding | The open command tower for AI engineering",
description:
- "Stop babysitting AI coding work. CortexPilot plans, delegates, tracks, resumes, and proves long-running engineering work across Codex and Claude Code with Workflow Cases, read-only MCP truth, and replayable proof.",
+ "Stop babysitting AI coding work. AI coding does not lack models. It lacks a command tower. OpenVibeCoding is the public shell for the CortexPilot runtime across Codex, Claude Code, Workflow Cases, and replayable proof.",
};
export const dynamic = "force-dynamic";
diff --git a/apps/dashboard/app/page.tsx b/apps/dashboard/app/page.tsx
index cac8e8f9..b9ed68cf 100644
--- a/apps/dashboard/app/page.tsx
+++ b/apps/dashboard/app/page.tsx
@@ -261,6 +261,12 @@ export default async function Home() {
: "metric-value--success";
const warningText =
firstEnglishText(warning) || "The run list is temporarily unavailable. Try again soon.";
+ const governanceDeckTitle =
+ locale === "zh-CN" ? "治理桌与放行控制" : "Governance desks and release controls";
+ const governanceDeckDescription =
+ locale === "zh-CN"
+ ? "这些页面承接审批、contract、role 和治理控制,但它们不再定义首页第一印象。"
+ : "These rooms handle approvals, contracts, role posture, and release controls, but they no longer define the homepage first impression.";
return (
@@ -279,6 +285,7 @@ export default async function Home() {
hasRunHistory={hasRunHistory}
latestFailureGovernanceHref={latestFailureGovernanceHref}
locale={locale}
+ runningCount={runningCount}
showFirstTaskGuide={!hasRunHistory}
/>
@@ -476,27 +483,27 @@ export default async function Home() {
- Advanced / Operator
+ {governanceDeckTitle}
-
These entry points keep the original governance power, but they now live in an advanced zone instead of defining the public first impression.
+
{governanceDeckDescription}
-
- Advanced
- Command Tower
- Watch sessions, alerts, and pipeline health.
-
-
- Operator
- Proof & Replay
- Inspect run details, compare reruns, replay evidence, and review governed actions.
-
Governance
Approvals and release control
Enter manual approval only when a review item requires it.
+
+ Authority
+ Contract desk
+ Inspect execution authority, bundle posture, and contract blockers before a run continues.
+
+
+ Role posture
+ Role desk
+ Check execution seats, runtime bindings, and scheduler posture without turning the homepage into a registry dump.
+
diff --git a/apps/dashboard/app/planner/page.tsx b/apps/dashboard/app/planner/page.tsx
new file mode 100644
index 00000000..d0faf88d
--- /dev/null
+++ b/apps/dashboard/app/planner/page.tsx
@@ -0,0 +1,453 @@
+import { cookies } from "next/headers";
+import type { Metadata } from "next";
+import Link from "next/link";
+import { normalizeUiLocale, UI_LOCALE_STORAGE_KEY } from "@cortexpilot/frontend-shared/uiLocale";
+import { Badge } from "../../components/ui/badge";
+import { Button } from "../../components/ui/button";
+import { Card } from "../../components/ui/card";
+import { fetchArtifact, fetchReports, fetchRun, fetchRuns } from "../../lib/api";
+import { safeLoad } from "../../lib/serverPageData";
+import type { JsonValue, ReportRecord, RunDetailPayload, RunSummary } from "../../lib/types";
+import WorkflowQueueMutationControls from "../workflows/WorkflowQueueMutationControls";
+
+export const metadata: Metadata = {
+ title: "Planner desk | OpenVibeCoding",
+ description:
+ "Triages wave plans, worker prompt contracts, unblock tasks, and continuation governance from one planner-facing control desk.",
+};
+
+type PlannerRow = {
+ run: RunDetailPayload;
+ wavePlan: Record | null;
+ workerContracts: Record[];
+ unblockTasks: Record[];
+ completionGovernance: Record | null;
+ plannedWorkerCount: number;
+};
+
+function asRecord(value: JsonValue | null | undefined): Record | null {
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : null;
+}
+
+function asRecordArray(value: JsonValue | null | undefined): Record[] {
+ return Array.isArray(value)
+ ? value.filter((item): item is Record => Boolean(item) && typeof item === "object" && !Array.isArray(item))
+ : [];
+}
+
+function artifactNames(run: RunDetailPayload): string[] {
+ const artifacts = Array.isArray(run.manifest?.artifacts) ? run.manifest?.artifacts : [];
+ return artifacts
+ .map((item) => {
+ const record = asRecord(item as JsonValue);
+ const name = typeof record?.name === "string" ? record.name : typeof record?.path === "string" ? record.path : "";
+ return String(name || "").trim();
+ })
+ .filter(Boolean);
+}
+
+async function resolveDashboardLocale() {
+ try {
+ const cookieStore = await cookies();
+ return normalizeUiLocale(cookieStore.get(UI_LOCALE_STORAGE_KEY)?.value);
+ } catch {
+ return normalizeUiLocale(undefined);
+ }
+}
+
+function plannerText(locale: "en" | "zh-CN") {
+ if (locale === "zh-CN") {
+ return {
+ title: "规划桌",
+ subtitle: "先做分诊,再看细节。把 wave plan、worker prompt contracts、wake policy 和 completion governance 放到同一张控制桌上,直接回答下一步该派谁、该回哪里、该继续还是该解阻塞。",
+ actions: {
+ pm: "打开 PM 入口",
+ tower: "打开 Command Tower",
+ workflows: "打开工作流案例",
+ proof: "打开 Proof & Replay",
+ },
+ metrics: {
+ runs: "带规划产物的 runs",
+ workers: "可见 worker contracts",
+ unblock: "可见 unblock tasks",
+ wake: "挂了 wake policy 的 runs",
+ },
+ table: {
+ run: "Run",
+ objective: "Wave objective",
+ blocker: "当前分诊",
+ next: "下一步",
+ },
+ empty: "当前还没有可见的规划产物。",
+ note: "这张桌子保持 `task_contract` 仍是唯一执行权威,但它已经不再只是 read-only 摆件:现在先做规划分诊,再决定回 PM、Workflow Cases、Command Tower 还是 Proof & Replay。",
+ openRun: "打开证明室",
+ openWorkflow: "打开工作流案例",
+ openTower: "打开 Command Tower",
+ openPm: "打开 PM 入口",
+ wakeLabel: "Wake policy",
+ governanceLabel: "Governance verdict",
+ unblockLabel: "Unblock tasks",
+ contractsLabel: "Worker contracts",
+ plannedWorkersLabel: "Planned workers",
+ triageTitle: "规划分诊队列",
+ triageSubtitle: "先确认哪条 wave 缺 worker contract、哪条已经进入 continuation、哪条该优先处理 unblock,再去下一张桌子。",
+ inspectionTitle: "规划细节档案",
+ inspectionSubtitle: "把原始 planning artifact 留在第二层阅读,同时保留 planner desk 自己的 queue / dispatch 控制,不再让它只是一个跳转台。",
+ blocker: {
+ missingGovernance: "缺 completion governance",
+ missingContracts: "缺 worker prompt contract",
+ hasUnblock: "有 unblock 任务待看",
+ continuation: "已选 continuation",
+ reviewProof: "回证明室看真实结果",
+ },
+ };
+ }
+ return {
+ title: "Planner desk",
+ subtitle:
+ "Start with planning triage. Bring wave plans, worker prompt contracts, wake-policy posture, and completion governance into one control desk so the operator can decide who moves next, what is blocked, and where truth lives.",
+ actions: {
+ pm: "Open PM intake",
+ tower: "Open Command Tower",
+ workflows: "Open Workflow Cases",
+ proof: "Open Proof & Replay",
+ },
+ metrics: {
+ runs: "Runs with planning artifacts",
+ workers: "Visible worker contracts",
+ unblock: "Visible unblock tasks",
+ wake: "Runs anchored to wake policy",
+ },
+ table: {
+ run: "Run",
+ objective: "Wave objective",
+ blocker: "Current triage",
+ next: "Next action",
+ },
+ empty: "No planning artifacts are visible yet.",
+ note: "The task contract still owns execution authority, but this desk now acts as the planning control layer: triage first, then decide whether to return to PM intake, Workflow Cases, Command Tower, or Proof & Replay.",
+ openRun: "Open run detail",
+ openWorkflow: "Open Workflow Cases",
+ openTower: "Open Command Tower",
+ openPm: "Open PM intake",
+ wakeLabel: "Wake policy",
+ governanceLabel: "Governance verdict",
+ unblockLabel: "Unblock tasks",
+ contractsLabel: "Worker contracts",
+ plannedWorkersLabel: "Planned workers",
+ triageTitle: "Planner triage queue",
+ triageSubtitle:
+ "Confirm which wave is missing worker contracts, which one already selected a continuation path, and which one should send you to unblock review next.",
+ inspectionTitle: "Planning inspection archive",
+ inspectionSubtitle:
+ "Keep the raw planning artifacts in a second layer while the desk itself keeps minimal queue and dispatch controls close to the triage row.",
+ blocker: {
+ missingGovernance: "Missing completion governance",
+ missingContracts: "Missing worker prompt contract",
+ hasUnblock: "Queued unblock tasks need review",
+ continuation: "Continuation already selected",
+ reviewProof: "Return to proof for live result review",
+ },
+ };
+}
+
+function plannerTriage(text: ReturnType, row: PlannerRow) {
+ const continuationDecision =
+ row.completionGovernance?.continuation_decision &&
+ typeof row.completionGovernance.continuation_decision === "object" &&
+ !Array.isArray(row.completionGovernance.continuation_decision)
+ ? (row.completionGovernance.continuation_decision as Record)
+ : null;
+ const selectedAction = String(continuationDecision?.selected_action || "").trim();
+
+ if (!row.completionGovernance) {
+ return {
+ label: text.blocker.missingGovernance,
+ nextLabel: text.openPm,
+ nextHref: "/pm",
+ secondaryLabel: text.openTower,
+ secondaryHref: "/command-tower",
+ };
+ }
+ if (row.workerContracts.length === 0 || row.plannedWorkerCount > row.workerContracts.length) {
+ return {
+ label: text.blocker.missingContracts,
+ nextLabel: text.openPm,
+ nextHref: "/pm",
+ secondaryLabel: text.openTower,
+ secondaryHref: "/command-tower",
+ };
+ }
+ if (row.unblockTasks.length > 0) {
+ return {
+ label: text.blocker.hasUnblock,
+ nextLabel: text.openWorkflow,
+ nextHref: "/workflows",
+ secondaryLabel: text.openRun,
+ secondaryHref: `/runs/${encodeURIComponent(String(row.run.run_id || ""))}`,
+ };
+ }
+ if (selectedAction && selectedAction !== "-") {
+ return {
+ label: `${text.blocker.continuation}: ${selectedAction}`,
+ nextLabel: text.openRun,
+ nextHref: `/runs/${encodeURIComponent(String(row.run.run_id || ""))}`,
+ secondaryLabel: text.openTower,
+ secondaryHref: "/command-tower",
+ };
+ }
+ return {
+ label: text.blocker.reviewProof,
+ nextLabel: text.openRun,
+ nextHref: `/runs/${encodeURIComponent(String(row.run.run_id || ""))}`,
+ secondaryLabel: text.openWorkflow,
+ secondaryHref: "/workflows",
+ };
+}
+
+async function loadPlannerRows(runs: RunSummary[]): Promise {
+ const candidateRuns = runs.slice(0, 8);
+ const rows = await Promise.all(
+ candidateRuns.map(async (run) => {
+ const runId = String(run.run_id || "").trim();
+ if (!runId) {
+ return null;
+ }
+ const runDetailResult = await safeLoad(
+ () => fetchRun(runId),
+ { run_id: runId, task_id: run.task_id, status: run.status } as RunDetailPayload,
+ `Run detail ${runId}`,
+ );
+ const detailedRun = runDetailResult.data;
+ const names = artifactNames(detailedRun);
+ const hasWavePlan =
+ names.includes("planning_wave_plan") || names.includes("artifacts/planning_wave_plan.json");
+ const hasWorkerContracts =
+ names.includes("planning_worker_prompt_contracts") ||
+ names.includes("artifacts/planning_worker_prompt_contracts.json");
+ const hasUnblockTasks =
+ names.includes("planning_unblock_tasks") || names.includes("artifacts/planning_unblock_tasks.json");
+ if (!(hasWavePlan || hasWorkerContracts || hasUnblockTasks)) {
+ return null;
+ }
+ const [wavePlanResult, workerContractsResult, unblockTasksResult, reportsResult] = await Promise.all([
+ hasWavePlan
+ ? safeLoad(() => fetchArtifact(runId, "planning_wave_plan.json"), null, `Wave plan ${runId}`)
+ : Promise.resolve({ data: null, warning: null }),
+ hasWorkerContracts
+ ? safeLoad(
+ () => fetchArtifact(runId, "planning_worker_prompt_contracts.json"),
+ null,
+ `Worker contracts ${runId}`,
+ )
+ : Promise.resolve({ data: null, warning: null }),
+ hasUnblockTasks
+ ? safeLoad(() => fetchArtifact(runId, "planning_unblock_tasks.json"), null, `Unblock tasks ${runId}`)
+ : Promise.resolve({ data: null, warning: null }),
+ safeLoad(() => fetchReports(runId), [] as ReportRecord[], `Reports ${runId}`),
+ ]);
+ const completionGovernanceRecord = (reportsResult.data as ReportRecord[]).find(
+ (report) => report.name === "completion_governance_report.json",
+ );
+ return {
+ run: detailedRun,
+ wavePlan: asRecord((wavePlanResult.data as { data?: JsonValue } | null)?.data),
+ workerContracts: asRecordArray((workerContractsResult.data as { data?: JsonValue } | null)?.data),
+ unblockTasks: asRecordArray((unblockTasksResult.data as { data?: JsonValue } | null)?.data),
+ completionGovernance: asRecord(completionGovernanceRecord?.data as JsonValue | undefined),
+ plannedWorkerCount: Number(
+ asRecord((wavePlanResult.data as { data?: JsonValue } | null)?.data)?.worker_count || 0,
+ ),
+ } satisfies PlannerRow;
+ }),
+ );
+ return rows.filter((row): row is PlannerRow => row !== null);
+}
+
+export default async function PlannerPage() {
+ const locale = await resolveDashboardLocale();
+ const text = plannerText(locale);
+ const { data: runs, warning } = await safeLoad(fetchRuns, [] as RunSummary[], "Run list");
+ const rows = await loadPlannerRows(Array.isArray(runs) ? runs : []);
+ const totalWorkerContracts = rows.reduce((sum, row) => sum + row.workerContracts.length, 0);
+ const totalUnblockTasks = rows.reduce((sum, row) => sum + row.unblockTasks.length, 0);
+ const wakeAnchoredRuns = rows.filter((row) => Boolean(row.wavePlan?.wake_policy_ref)).length;
+
+ return (
+
+
+
+
+
+ {text.metrics.runs}
+ {rows.length}
+
+
+ {text.metrics.workers}
+ {totalWorkerContracts}
+
+
+ {text.metrics.unblock}
+ {totalUnblockTasks}
+
+
+ {text.metrics.wake}
+ {wakeAnchoredRuns}
+
+
+
+ {warning ? (
+
+ {warning}
+
+ ) : null}
+
+ {rows.length === 0 ? (
+
+
+ {text.empty}
+ {text.note}
+
+
+ ) : (
+
+
+
+
+
{text.triageTitle}
+
{text.triageSubtitle}
+
+
+
+
+
+ {text.table.run}
+ {text.table.objective}
+ {text.table.blocker}
+ {text.table.next}
+
+
+
+ {rows.map((row) => {
+ const runId = String(row.run.run_id || "-");
+ const objective = String(row.wavePlan?.objective || row.run.task_id || "-").trim() || "-";
+ const triage = plannerTriage(text, row);
+ return (
+
+
+
+ {runId}
+ {String(row.run.workflow_status || row.run.status || "-")}
+
+
+
+
+ {objective}
+
+ {text.wakeLabel}: {String(row.wavePlan?.wake_policy_ref || "-")}
+
+
+ {text.governanceLabel}: {String(row.completionGovernance?.overall_verdict || "-")}
+
+
+ {text.plannedWorkersLabel}: {row.plannedWorkerCount || 0} · {text.contractsLabel}: {row.workerContracts.length} · {text.unblockLabel}: {row.unblockTasks.length}
+
+
+
+
+ 0 ? "warning" : row.completionGovernance ? "running" : "failed"}>
+ {triage.label}
+
+
+
+
+
+ {triage.nextLabel}
+
+
+ {triage.secondaryLabel}
+
+
+
+
+
+ );
+ })}
+
+
+ {text.note}
+
+
+
+ {text.inspectionTitle}
+
+
{text.inspectionSubtitle}
+
+ {rows.map((row) => {
+ const runId = String(row.run.run_id || "-");
+ const objective = String(row.wavePlan?.objective || row.run.task_id || "-").trim() || "-";
+ const continuationSummary = String(
+ row.completionGovernance?.continuation_decision &&
+ typeof row.completionGovernance.continuation_decision === "object" &&
+ !Array.isArray(row.completionGovernance.continuation_decision)
+ ? (row.completionGovernance.continuation_decision as Record
).selected_action || "-"
+ : "-",
+ );
+ return (
+
+
+ {objective}
+ {runId}
+ {text.wakeLabel}: {String(row.wavePlan?.wake_policy_ref || "-")}
+ {text.governanceLabel}: {String(row.completionGovernance?.overall_verdict || "-")}
+ {text.plannedWorkersLabel}: {row.plannedWorkerCount || 0}
+ {text.contractsLabel}: {row.workerContracts.length}
+ {text.unblockLabel}: {row.unblockTasks.length}
+ Continuation: {continuationSummary}
+
+
+
+ );
+ })}
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/dashboard/app/runs/[id]/compare/page.tsx b/apps/dashboard/app/runs/[id]/compare/page.tsx
index 33876b30..87e3b1e2 100644
--- a/apps/dashboard/app/runs/[id]/compare/page.tsx
+++ b/apps/dashboard/app/runs/[id]/compare/page.tsx
@@ -157,21 +157,33 @@ export default async function RunComparePage({
{proofPack.summary ? Proof: {String(proofPack.summary)}
: null}
-
- Run compare report
- {JSON.stringify(runCompareReport, null, 2)}
-
-
- Compare summary
- {JSON.stringify(compareSummary, null, 2)}
-
-
- Replay report
- {JSON.stringify(replayReport, null, 2)}
-
-
- Run snapshot
- {JSON.stringify(run, null, 2)}
+
+
+ Evidence archive
+
+
+ Keep the first screen decision-first. Open the raw compare payload only when you need to inspect the underlying proof.
+
+
+
+ Run compare report
+ {JSON.stringify(runCompareReport, null, 2)}
+
+
+ Compare summary
+ {JSON.stringify(compareSummary, null, 2)}
+
+
+ Replay report
+ {JSON.stringify(replayReport, null, 2)}
+
+
+ Run snapshot
+ {JSON.stringify(run, null, 2)}
+
+
+
+
diff --git a/apps/dashboard/app/runs/page.tsx b/apps/dashboard/app/runs/page.tsx
index 4e21b530..ee10de88 100644
--- a/apps/dashboard/app/runs/page.tsx
+++ b/apps/dashboard/app/runs/page.tsx
@@ -1,5 +1,6 @@
import { cookies } from "next/headers";
import Link from "next/link";
+import type { Metadata } from "next";
import { getUiCopy } from "@cortexpilot/frontend-shared/uiCopy";
import { normalizeUiLocale, UI_LOCALE_STORAGE_KEY } from "@cortexpilot/frontend-shared/uiLocale";
@@ -15,6 +16,12 @@ type RunsPageProps = {
searchParams?: Promise>;
};
+export const metadata: Metadata = {
+ title: "Proof & Replay | OpenVibeCoding",
+ description:
+ "Inspect latest outcomes, replay posture, failure clues, and next operator actions from the OpenVibeCoding proof and replay surface.",
+};
+
function queryValue(value: string | string[] | undefined): string {
return Array.isArray(value) ? String(value[0] || "").trim() : String(value || "").trim();
}
@@ -77,6 +84,7 @@ export default async function RunsPage({ searchParams }: RunsPageProps) {
+
OpenVibeCoding / proof and replay
{runsPageCopy.title}
{runsPageCopy.subtitle}
@@ -94,26 +102,6 @@ export default async function RunsPage({ searchParams }: RunsPageProps) {
{operatorDeskNote}
-
-
- {runsPageCopy.filters.all}
-
-
- {runsPageCopy.filters.failed}
-
-
- {runsPageCopy.filters.running}
-
-
- {runsPageCopy.filters.success}
-
-
- {runs.length > visibleRuns.length ? (
-
- {runsPageCopy.firstScreenLimit(visibleRuns.length)}
-
- ) : null}
-
@@ -147,6 +135,26 @@ export default async function RunsPage({ searchParams }: RunsPageProps) {
+
+
+ {runsPageCopy.filters.all}
+
+
+ {runsPageCopy.filters.failed}
+
+
+ {runsPageCopy.filters.running}
+
+
+ {runsPageCopy.filters.success}
+
+
+ {runs.length > visibleRuns.length ? (
+
+ {runsPageCopy.firstScreenLimit(visibleRuns.length)}
+
+ ) : null}
+
);
diff --git a/apps/dashboard/app/workflows/[id]/page.tsx b/apps/dashboard/app/workflows/[id]/page.tsx
index 3834bdc3..57309a2a 100644
--- a/apps/dashboard/app/workflows/[id]/page.tsx
+++ b/apps/dashboard/app/workflows/[id]/page.tsx
@@ -84,9 +84,9 @@ export async function generateMetadata({
const workflowId = safeDecodeParam(id);
const titleSuffix = workflowId ? ` · ${workflowId}` : "";
return {
- title: `Workflow Case detail${titleSuffix} | CortexPilot`,
+ title: `Workflow Case detail${titleSuffix} | OpenVibeCoding`,
description:
- "Inspect one Workflow Case across risk, queue posture, linked runs, event timeline, and the next operator action inside CortexPilot.",
+ "Inspect one Workflow Case across risk, queue posture, linked runs, event timeline, and the next operator action inside the OpenVibeCoding command tower.",
};
}
@@ -143,6 +143,7 @@ export default async function WorkflowDetailPage({