Skip to content

Commit e4a825b

Browse files
committed
feat: align local command tower operator loop
1 parent 68a71f6 commit e4a825b

14 files changed

Lines changed: 892 additions & 161 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ All notable changes to this repository will be documented in this file.
66

77
### Changed
88

9+
- corrected the default localhost full-stack operator path so `npm run dev`
10+
now truthfully pairs the dashboard with a localhost-only API lane, while
11+
`dashboard:dev` stays a dashboard-only shell on the expected port; the same
12+
slice also pulls the dashboard `Runs / Contracts / Agents` first-screen copy
13+
back into shared command-tower proof / contract / role-desk language instead
14+
of leaving those pages on scattered inspector phrasing
915
- pinned transitive dashboard security fixes through the maintained pnpm
1016
override surfaces so `axios` now resolves to `1.15.0` and `basic-ftp` to
1117
`5.2.2` in both the root and dashboard lockfiles, clearing the live

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ paths:
7676

7777
| I want to... | Run this first | What I get |
7878
| --- | --- | --- |
79-
| see the operator surface quickly | `npm run bootstrap:host && npm run dashboard:dev` | the PM surface, Command Tower, and run visibility in one local product loop |
79+
| see the full operator surface quickly | `npm run bootstrap:host && npm run dev` | the localhost orchestrator API plus dashboard in one local product loop |
80+
| iterate on the dashboard only | `npm run bootstrap:host && npm run dashboard:dev` | the dashboard shell on port 3100; use this when the API is already running |
8081
| validate the smallest governed path | `CORTEXPILOT_HOST_COMPAT=1 bash scripts/test_quick.sh --no-related` | the quickest repo-side proof path without pretending the full system already ran |
8182
| inspect what the system records | open the run list and `.runtime-cache/` after the quick path | a concrete evidence bundle and replay surface, not just a shell success line |
8283

@@ -152,18 +153,27 @@ manual:
152153
CORTEXPILOT_HOST_COMPAT=1 bash scripts/test_quick.sh --no-related
153154
```
154155

155-
3. Open the web operator surface:
156+
3. Open the full local product loop:
156157

157158
```bash
158-
npm run dashboard:dev
159+
npm run dev
159160
```
160161

162+
This path starts a localhost-only API lane together with the dashboard so the browser can exercise PM, Command Tower, Workflow Cases, and Runs without sending a public bearer token from the client.
163+
161164
What you should see:
162165

163166
- create a task from the PM surface
164167
- watch status move in Command Tower
165168
- confirm the Workflow Case state, then inspect runs, reports, and evidence from the run list
166169

170+
If you only need the dashboard shell while the API is already running in another
171+
terminal, use:
172+
173+
```bash
174+
npm run dashboard:dev
175+
```
176+
167177
If you want the full reproducible containerized setup instead of the shortest
168178
host path, use:
169179

@@ -560,6 +570,7 @@ Recent operator-surface upgrades now include:
560570
Useful additional entrypoints:
561571

562572
```bash
573+
npm run dev
563574
npm run space:audit
564575
npm run space:gate:wave1
565576
npm run space:gate:wave2
@@ -569,6 +580,10 @@ npm run desktop:up
569580
npm run truth:triage
570581
```
571582

583+
Use `npm run dev` when you want the orchestrator API and dashboard together.
584+
Keep `npm run dashboard:dev` for dashboard-only iteration after the API is
585+
already running.
586+
572587
## Generated Governance Context
573588

574589
<!-- GENERATED:ci-topology-summary:start -->

apps/dashboard/app/agents/page.tsx

Lines changed: 117 additions & 83 deletions
Large diffs are not rendered by default.

apps/dashboard/app/contracts/page.tsx

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { cookies } from "next/headers";
2+
import { getUiCopy } from "@cortexpilot/frontend-shared/uiCopy";
3+
import { normalizeUiLocale, UI_LOCALE_STORAGE_KEY } from "@cortexpilot/frontend-shared/uiLocale";
14
import { Badge } from "../../components/ui/badge";
25
import { Button } from "../../components/ui/button";
36
import { Card, CardContent, CardHeader } from "../../components/ui/card";
@@ -20,11 +23,22 @@ function summarizeToolPermissions(value: unknown): string[] {
2023

2124
const DEFAULT_CONTRACT_LIMIT = 10;
2225

26+
async function resolveDashboardLocale() {
27+
try {
28+
const cookieStore = await cookies();
29+
return normalizeUiLocale(cookieStore.get(UI_LOCALE_STORAGE_KEY)?.value);
30+
} catch {
31+
return normalizeUiLocale(undefined);
32+
}
33+
}
34+
2335
export default async function ContractsPage({
2436
searchParams,
2537
}: {
2638
searchParams?: Promise<{ q?: string; limit?: string }>;
2739
}) {
40+
const locale = await resolveDashboardLocale();
41+
const contractsPageCopy = getUiCopy(locale).dashboard.contractsPage;
2842
const { data: contracts, warning } = await safeLoad(fetchContracts, [] as ContractRecord[], "Contract list");
2943
const resolvedSearchParams = (await searchParams) || {};
3044
const query = String(resolvedSearchParams.q || "").trim().toLowerCase();
@@ -44,34 +58,40 @@ export default async function ContractsPage({
4458
<header className="app-section">
4559
<div className="section-header">
4660
<div>
47-
<h1 id="contracts-page-title" className="page-title">Contracts</h1>
48-
<p className="page-subtitle">Inspect task-contract envelopes, derived bundle/runtime projections, and raw artifact detail in one read-only surface.</p>
61+
<h1 id="contracts-page-title" className="page-title">{contractsPageCopy.title}</h1>
62+
<p className="page-subtitle">{contractsPageCopy.subtitle}</p>
4963
</div>
50-
<Badge>{contracts.length} contracts</Badge>
64+
<Badge>{contractsPageCopy.countsBadge(contracts.length)}</Badge>
5165
</div>
5266
</header>
5367
<section className="app-section" aria-label="Contract list">
5468
<Card>
5569
<form className="toolbar" method="get">
5670
<label className="diff-gate-filter-field">
57-
<span className="muted">Search</span>
58-
<Input type="search" name="q" defaultValue={query} placeholder="Filter by task_id / run_id / path" />
71+
<span className="muted">{contractsPageCopy.searchLabel}</span>
72+
<Input type="search" name="q" defaultValue={query} placeholder={contractsPageCopy.searchPlaceholder} />
5973
</label>
6074
<input type="hidden" name="limit" value={String(limit)} />
61-
<Button type="submit" variant="secondary" disabled={!canApplyFilter}>Apply filter</Button>
75+
<Button type="submit" variant="secondary" disabled={!canApplyFilter}>{contractsPageCopy.applyFilter}</Button>
6276
</form>
63-
<p className="mono muted">Showing {visibleContracts.length} / {filteredContracts.length} contracts. Default first-page limit: {DEFAULT_CONTRACT_LIMIT}.</p>
77+
<p className="mono muted">
78+
{contractsPageCopy.filterSummary(visibleContracts.length, filteredContracts.length, DEFAULT_CONTRACT_LIMIT)}
79+
</p>
6480
</Card>
6581
{warning ? (
6682
<Card asChild variant="unstyled">
67-
<p className="alert alert-warning" role="status">{warning}</p>
83+
<div className="alert alert-warning" role="status">
84+
<p>{contractsPageCopy.warningTitle}</p>
85+
<p className="mono">{contractsPageCopy.warningNextStep}</p>
86+
<p className="mono">{warning}</p>
87+
</div>
6888
</Card>
6989
) : null}
7090
{filteredContracts.length === 0 ? (
7191
<Card>
7292
<div className="empty-state-stack">
73-
<span className="muted">No contracts yet</span>
74-
<span className="mono muted">Contracts are generated automatically when work is assigned.</span>
93+
<span className="muted">{contractsPageCopy.emptyTitle}</span>
94+
<span className="mono muted">{contractsPageCopy.emptyHint}</span>
7595
</div>
7696
</Card>
7797
) : (
@@ -87,65 +107,81 @@ export default async function ContractsPage({
87107
return (
88108
<Card key={key} variant="detail">
89109
<CardHeader>
90-
<span className="card-header-title">{contract.path || contract.task_id || "Contract"}</span>
91-
<Badge>{contract.source || "unknown"}</Badge>
110+
<span className="card-header-title">
111+
{contract.path || contract.task_id || contractsPageCopy.fallbackValues.unknownContract}
112+
</span>
113+
<Badge>{contract.source || contractsPageCopy.fallbackValues.unknownSource}</Badge>
92114
</CardHeader>
93115
<CardContent>
94116
<div className="data-list">
95117
{contract.task_id ? (
96118
<div className="data-list-row">
97-
<span className="data-list-label">Task ID</span>
119+
<span className="data-list-label">{contractsPageCopy.fieldLabels.taskId}</span>
98120
<span className="data-list-value mono">{contract.task_id}</span>
99121
</div>
100122
) : null}
101123
{contract.run_id ? (
102124
<div className="data-list-row">
103-
<span className="data-list-label">Run ID</span>
125+
<span className="data-list-label">{contractsPageCopy.fieldLabels.runId}</span>
104126
<span className="data-list-value mono">{contract.run_id}</span>
105127
</div>
106128
) : null}
107129
<div className="data-list-row">
108-
<span className="data-list-label">Assigned role</span>
109-
<span className="data-list-value">{contract.assigned_role || "Not assigned"}</span>
130+
<span className="data-list-label">{contractsPageCopy.fieldLabels.assignedRole}</span>
131+
<span className="data-list-value">
132+
{contract.assigned_role || contractsPageCopy.fallbackValues.notAssigned}
133+
</span>
110134
</div>
111135
<div className="data-list-row">
112-
<span className="data-list-label">Execution authority</span>
136+
<span className="data-list-label">{contractsPageCopy.fieldLabels.executionAuthority}</span>
113137
<span className="data-list-value">
114-
{contract.execution_authority ? <Badge variant="running">{contract.execution_authority}</Badge> : <span className="muted">Not published</span>}
138+
{contract.execution_authority ? (
139+
<Badge variant="running">{contract.execution_authority}</Badge>
140+
) : (
141+
<span className="muted">{contractsPageCopy.fallbackValues.notPublished}</span>
142+
)}
115143
</span>
116144
</div>
117145
<div className="data-list-row">
118-
<span className="data-list-label">Skills bundle</span>
146+
<span className="data-list-label">{contractsPageCopy.fieldLabels.skillsBundle}</span>
119147
<span className="data-list-value mono muted">
120-
{roleBinding ? formatBindingReadModelLabel(roleBinding.skills_bundle_ref) : "Not derived"}
148+
{roleBinding
149+
? formatBindingReadModelLabel(roleBinding.skills_bundle_ref)
150+
: contractsPageCopy.fallbackValues.notDerived}
121151
</span>
122152
</div>
123153
<div className="data-list-row">
124-
<span className="data-list-label">MCP bundle</span>
154+
<span className="data-list-label">{contractsPageCopy.fieldLabels.mcpBundle}</span>
125155
<span className="data-list-value mono muted">
126-
{roleBinding ? formatBindingReadModelLabel(roleBinding.mcp_bundle_ref) : "Not derived"}
156+
{roleBinding
157+
? formatBindingReadModelLabel(roleBinding.mcp_bundle_ref)
158+
: contractsPageCopy.fallbackValues.notDerived}
127159
</span>
128160
</div>
129161
<div className="data-list-row">
130-
<span className="data-list-label">Runtime binding</span>
162+
<span className="data-list-label">{contractsPageCopy.fieldLabels.runtimeBinding}</span>
131163
<span className="data-list-value mono muted">
132-
{roleBinding ? formatRoleBindingRuntimeSummary(roleBinding) : "Not derived"}
164+
{roleBinding
165+
? formatRoleBindingRuntimeSummary(roleBinding)
166+
: contractsPageCopy.fallbackValues.notDerived}
133167
</span>
134168
</div>
135169
<div className="data-list-row">
136-
<span className="data-list-label">Runtime capability</span>
170+
<span className="data-list-label">{contractsPageCopy.fieldLabels.runtimeCapability}</span>
137171
<span className="data-list-value mono muted">
138-
{roleBinding?.runtime_binding?.capability?.lane || "Not derived"}
172+
{roleBinding?.runtime_binding?.capability?.lane || contractsPageCopy.fallbackValues.notDerived}
139173
</span>
140174
</div>
141175
<div className="data-list-row">
142-
<span className="data-list-label">Tool execution</span>
176+
<span className="data-list-label">{contractsPageCopy.fieldLabels.toolExecution}</span>
143177
<span className="data-list-value mono muted">
144-
{roleBinding ? formatRoleBindingRuntimeCapabilitySummary(roleBinding) : "Not derived"}
178+
{roleBinding
179+
? formatRoleBindingRuntimeCapabilitySummary(roleBinding)
180+
: contractsPageCopy.fallbackValues.notDerived}
145181
</span>
146182
</div>
147183
<div className="data-list-row">
148-
<span className="data-list-label">Allowed paths</span>
184+
<span className="data-list-label">{contractsPageCopy.fieldLabels.allowedPaths}</span>
149185
<span className="data-list-value">
150186
{allowedPaths.length > 0 ? (
151187
<span className="chip-list">
@@ -154,12 +190,12 @@ export default async function ContractsPage({
154190
))}
155191
</span>
156192
) : (
157-
<span className="muted">Unrestricted</span>
193+
<span className="muted">{contractsPageCopy.fallbackValues.unrestricted}</span>
158194
)}
159195
</span>
160196
</div>
161197
<div className="data-list-row">
162-
<span className="data-list-label">Acceptance tests</span>
198+
<span className="data-list-label">{contractsPageCopy.fieldLabels.acceptanceTests}</span>
163199
<span className="data-list-value">
164200
{acceptanceTests.length > 0 ? (
165201
<span className="chip-list">
@@ -168,12 +204,12 @@ export default async function ContractsPage({
168204
))}
169205
</span>
170206
) : (
171-
<span className="muted">None</span>
207+
<span className="muted">{contractsPageCopy.fallbackValues.noAcceptanceTests}</span>
172208
)}
173209
</span>
174210
</div>
175211
<div className="data-list-row">
176-
<span className="data-list-label">Tool permissions</span>
212+
<span className="data-list-label">{contractsPageCopy.fieldLabels.toolPermissions}</span>
177213
<span className="data-list-value">
178214
{permissionSummary.length > 0 ? (
179215
<span className="chip-list">
@@ -182,14 +218,14 @@ export default async function ContractsPage({
182218
))}
183219
</span>
184220
) : (
185-
<span className="muted">Default</span>
221+
<span className="muted">{contractsPageCopy.fallbackValues.defaultPermissions}</span>
186222
)}
187223
</span>
188224
</div>
189225
</div>
190226
</CardContent>
191227
<details className="collapsible">
192-
<summary>Full contract JSON</summary>
228+
<summary>{contractsPageCopy.fullJsonSummary}</summary>
193229
<div className="collapsible-body">
194230
<pre className="mono">{JSON.stringify(payload || { raw_preview: contract.raw_preview }, null, 2)}</pre>
195231
</div>
@@ -201,10 +237,12 @@ export default async function ContractsPage({
201237
)}
202238
{filteredContracts.length > limit ? (
203239
<Card>
204-
<p className="mono muted">{filteredContracts.length - limit} more contracts are hidden.</p>
240+
<p className="mono muted">{contractsPageCopy.moreHidden(filteredContracts.length - limit)}</p>
205241
<div className="toolbar mt-2">
206242
<Button asChild variant="secondary">
207-
<a href={`/contracts?${new URLSearchParams({ q: query, limit: String(filteredContracts.length) }).toString()}`}>Show all</a>
243+
<a href={`/contracts?${new URLSearchParams({ q: query, limit: String(filteredContracts.length) }).toString()}`}>
244+
{contractsPageCopy.showAll}
245+
</a>
208246
</Button>
209247
</div>
210248
</Card>

0 commit comments

Comments
 (0)