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
31 changes: 15 additions & 16 deletions console/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ function repaintRoster(): void {
async function tick(): Promise<void> {
if (!roster) return;
try {
const all = await source.listDeployments(activeCluster);
const all = await source.listDeployments(activeCluster, activeFleet ?? undefined);
// Filter to the active fleet's members (empty ⇒ whole cluster).
const deployments = filterByMembers(all, activeMembers);
lastDeployments = deployments;
Expand Down Expand Up @@ -450,24 +450,23 @@ function selectFleet(name: string): void {
if (!name || name === activeFleet) return;
const fleet = fleetConfig?.fleets.find((f) => f.name === name);
if (!fleet) return;
// k8s fleets have no ECS cluster to point reads at — listDeployments/
// runtimeContext/tick are all cluster-keyed and ecs-only today (a separate,
// larger gap: k8s fleets have no roster/observe UI yet, tracked apart from
// this schema unification). Don't pretend a switch worked when reads would
// silently break against an empty cluster string.
if (fleet.runtime === "k8s" || fleet.cluster === null) {
note(
"info",
`fleet "${name}" is a k8s fleet — switching the roster view to it isn't wired up yet`,
);
return;
}
closeOpenAgentConsole();
activeFleet = name;
activeCluster = fleet.cluster;
activeMembers = fleet.members;
if (clusterLabel) clusterLabel.textContent = `${activeFleet} · ${activeCluster}`;
note("info", `config: switched to fleet "${activeFleet}" (cluster "${activeCluster}")`);
if (fleet.runtime === "k8s") {
// No ECS cluster for a k8s fleet — reads now go by the `fleet` name
// instead (oab-mcp resolves the bound context/namespace itself,
// studio#146 slices 2-3); `activeCluster` stays a plain "" sentinel,
// never read on this path.
activeCluster = "";
const where = `${fleet.context ?? "current-context"}/${fleet.namespace ?? "default"}`;
if (clusterLabel) clusterLabel.textContent = `${activeFleet} · ${where}`;
note("info", `config: switched to fleet "${activeFleet}" (k8s ${where})`);
} else {
activeCluster = fleet.cluster ?? DEFAULT_CLUSTER;
if (clusterLabel) clusterLabel.textContent = `${activeFleet} · ${activeCluster}`;
note("info", `config: switched to fleet "${activeFleet}" (cluster "${activeCluster}")`);
}
if (configEl) renderFleetConfig(configEl, fleetConfig, activeFleet);
updateScreen();
void tick();
Expand Down
17 changes: 11 additions & 6 deletions console/src/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ import {
// A read source for the console. Desktop (Tauri → studio-cp) and the standalone
// browser build implement this identically, so the UI never knows which it is.
export interface Source {
listDeployments(cluster?: string): Promise<Deployment[]>;
runtimeContext(cluster?: string): Promise<RuntimeContext>;
// `fleet` (studio#146 slices 2-4) is required to reach a k8s-runtime
// fleet's roster/identity — `cluster` has no k8s equivalent to resolve
// against, since a k8s fleet targets a (context, namespace) instead. Safe
// to pass alongside `cluster` for an ecs-runtime fleet too: oab-mcp
// resolves the fleet's own cluster from the binding either way.
listDeployments(cluster?: string, fleet?: string): Promise<Deployment[]>;
runtimeContext(cluster?: string, fleet?: string): Promise<RuntimeContext>;
fleetConfig(): Promise<FleetConfig>;
// Persist the raw TOML `text` of the config file, returning the reloaded
// config. Rejects (without writing) when the text doesn't parse.
Expand Down Expand Up @@ -158,11 +163,11 @@ export class TauriSource implements Source {
if (!invoke) throw new Error("Tauri bridge unavailable");
return invoke;
}
async listDeployments(cluster?: string): Promise<Deployment[]> {
return this.invoke()<Deployment[]>("deploy_list", { cluster });
async listDeployments(cluster?: string, fleet?: string): Promise<Deployment[]> {
return this.invoke()<Deployment[]>("deploy_list", { cluster, fleet });
}
async runtimeContext(cluster?: string): Promise<RuntimeContext> {
return this.invoke()<RuntimeContext>("runtime_context", { cluster });
async runtimeContext(cluster?: string, fleet?: string): Promise<RuntimeContext> {
return this.invoke()<RuntimeContext>("runtime_context", { cluster, fleet });
}
async fleetConfig(): Promise<FleetConfig> {
return this.invoke()<FleetConfig>("fleet_config");
Expand Down
23 changes: 16 additions & 7 deletions console/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ export interface Deployment {
instances: InstancePhase[];
}

// The fleet binding in effect for a cluster (ADR #19). Mirrors oab-mcp's
// `runtime_context.binding`.
// The fleet binding in effect for a cluster or k8s context (ADR #19).
// Mirrors oab-mcp's `runtime_context.binding` — `profile`/`region` for an
// ecs-runtime fleet, `context`/`namespace` for a k8s-runtime one (studio#146
// slice 2); the other set is absent for a given entry.
export interface FleetBinding {
name: string;
profile: string | null;
region: string | null;
profile?: string | null;
region?: string | null;
context?: string | null;
namespace?: string | null;
expected_principal: string | null;
}

Expand Down Expand Up @@ -160,10 +164,15 @@ export interface FsCapability {
}

// The effective runtime identity/context the control plane resolved for a
// cluster — mirrors oab-mcp's `runtime_context` tool (ADR #19). `identity_matches`
// is `null` when the binding declares no `expected_principal`.
// cluster or k8s-runtime fleet — mirrors oab-mcp's `runtime_context` tool
// (ADR #19). `cluster` is `null` for a k8s-runtime fleet (`context`/
// `namespace` are set instead, studio#146 slice 2) — always check `cluster`
// before assuming an ecs shape. `identity_matches` is `null` when the
// binding declares no `expected_principal`.
export interface RuntimeContext {
cluster: string;
cluster: string | null;
context?: string | null;
namespace?: string | null;
principal: string;
principal_kind: string; // "role" | "user" | "unknown"
scope: string; // AWS account id
Expand Down
Loading