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
5 changes: 2 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,17 @@
"@aws-sdk/client-bedrock-agentcore": "^3.1079.0",
"@aws-sdk/client-bedrock-agentcore-control": "^3.1079.0",
"@aws-sdk/client-iam": "^3.1080.0",
"@inkui-cli/data-table": "^0.2.0",
"@smithy/core": "3.29.3",
"@tanstack/react-query": "^5.101.2",
"cli-truncate": "^6.1.1",
"commander": "^15.0.0",
"ink": "^7.1.0",
"ink-scroll-view": "^0.3.7",
"lodash": "^4.18.1",
"react": "^19.2.7",
"react-devtools-core": "^7.0.1",
"react-router": "^8.3.0",
"string-width": "^8.2.2",
"winston": "^3.19.0",
"winston-daily-rotate-file": "^5.0.0",
"zod": "^4.4.3"
Expand Down
23 changes: 16 additions & 7 deletions src/components/HarnessEndpointPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { useNavigate } from "react-router";
import type { HarnessEndpoint } from "@aws-sdk/client-bedrock-agentcore-control";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

// EndpointRow is the flat, display-ready shape the table renders.
interface EndpointRow extends Record<string, unknown> {
Expand All @@ -13,6 +15,19 @@ interface EndpointRow extends Record<string, unknown> {
updatedAt: string;
}

export const harnessEndpointColumns = [
{ key: "endpointName", header: "name", flex: true },
{ key: "liveVersion", header: "live", width: 6, minWidth: 5 },
{ key: "targetVersion", header: "target", width: 6 },
{ key: "status", header: "status", width: 13 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<EndpointRow>[];

function toRow(e: HarnessEndpoint): EndpointRow {
return {
endpointName: e.endpointName!,
Expand Down Expand Up @@ -70,13 +85,7 @@ export function HarnessEndpointPicker({
};
}}
toRow={toRow}
columns={[
{ key: "endpointName", header: "name" },
{ key: "liveVersion", header: "live" },
{ key: "targetVersion", header: "target" },
{ key: "status", header: "status" },
{ key: "updatedAt", header: "updatedAt" },
]}
columns={harnessEndpointColumns}
getValue={(row) => row.endpointName}
onSelect={onSelect}
onBack={goBack}
Expand Down
21 changes: 15 additions & 6 deletions src/components/HarnessPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { useNavigate } from "react-router";
import type { HarnessSummary } from "@aws-sdk/client-bedrock-agentcore-control";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

// HarnessRow is the flat, display-ready shape the table renders. It also satisfies
// DataTable's `T extends Record<string, unknown>` constraint, which the SDK's
Expand All @@ -15,6 +17,18 @@ interface HarnessRow extends Record<string, unknown> {
status: string;
}

export const harnessColumns = [
{ key: "harnessName", header: "name", flex: true },
{ key: "harnessVersion", header: "version", width: 7 },
{ key: "status", header: "status", width: 13 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<HarnessRow>[];

// toRow flattens a HarnessSummary into a HarnessRow, formatting dates.
function toRow(h: HarnessSummary): HarnessRow {
return {
Expand Down Expand Up @@ -68,12 +82,7 @@ export function HarnessPicker({
};
}}
toRow={toRow}
columns={[
{ key: "harnessName", header: "name" },
{ key: "harnessVersion", header: "version" },
{ key: "status", header: "status" },
{ key: "updatedAt", header: "updatedAt" },
]}
columns={harnessColumns}
getValue={(row) => row.harnessId}
onSelect={onSelect}
onBack={goBack}
Expand Down
20 changes: 15 additions & 5 deletions src/components/HarnessVersionPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { useNavigate } from "react-router";
import type { HarnessVersionSummary } from "@aws-sdk/client-bedrock-agentcore-control";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

// VersionRow is the flat, display-ready shape the table renders.
interface VersionRow extends Record<string, unknown> {
Expand All @@ -12,6 +14,18 @@ interface VersionRow extends Record<string, unknown> {
updatedAt: string;
}

export const harnessVersionColumns = [
{ key: "harnessVersion", header: "version", width: 7 },
{ key: "status", header: "status", width: 13, minWidth: 6 },
{
key: "createdAt",
header: "created UTC",
width: 16,
minWidth: 11,
render: formatTimestamp,
},
] satisfies DataTableColumn<VersionRow>[];

function toRow(v: HarnessVersionSummary): VersionRow {
return {
harnessVersion: v.harnessVersion!,
Expand Down Expand Up @@ -62,11 +76,7 @@ export function HarnessVersionPicker({
};
}}
toRow={toRow}
columns={[
{ key: "harnessVersion", header: "version" },
{ key: "status", header: "status" },
{ key: "createdAt", header: "createdAt" },
]}
columns={harnessVersionColumns}
sortRows={(rows) =>
[...rows].sort((left, right) => Number(right.harnessVersion) - Number(left.harnessVersion))
}
Expand Down
65 changes: 65 additions & 0 deletions src/components/PaginatedTablePicker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { afterEach, describe, expect, test } from "bun:test";
import type {
AgentRuntime,
HarnessSummary,
ListHarnessesResponse,
} from "@aws-sdk/client-bedrock-agentcore-control";
import { QueryClient } from "@tanstack/react-query";
import stringWidth from "string-width";
import { cleanupScreens, renderScreen, TestCoreClient, waitFor, waitForText } from "../testing";

afterEach(cleanupScreens);
Expand All @@ -27,6 +29,19 @@ function coreWith(harnesses: HarnessSummary[]): TestCoreClient {
return core;
}

function runtime(overrides: Partial<AgentRuntime> = {}): AgentRuntime {
return {
agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/orders-AbCdEf1234",
agentRuntimeId: "orders-AbCdEf1234",
agentRuntimeVersion: "99999",
agentRuntimeName: "orders-runtime-with-a-long-name",
description: "Orders Runtime",
lastUpdatedAt: new Date("2026-07-19T01:02:03.000Z"),
status: "CREATE_FAILED",
...overrides,
};
}

function getResponse(summary: HarnessSummary) {
return { harness: summary } as Parameters<TestCoreClient["harness"]["setGetResponse"]>[0];
}
Expand Down Expand Up @@ -287,4 +302,54 @@ describe("paginated table picker contract", () => {
);
expect(r.lastFrame()).toContain("agentcore → harness → get → alpha-1");
});

test("keeps rows aligned and single-line while resizing the Runtime table", async () => {
const core = new TestCoreClient();
const suffixes = ["AbCdEf1234", "BcDeFg2345", "CdEfGh3456"];
core.runtime.setListResponse({
agentRuntimes: suffixes.map((suffix, index) =>
runtime({
agentRuntimeId: `runtime_${index}-${suffix}`,
agentRuntimeName: `runtime_${index}_with_a_name_that_needs_truncation`,
}),
),
});
const r = renderScreen("/agentcore/runtime/list", { core });

await waitForText(r.lastFrame, suffixes[0]!);
for (const width of [100, 80, 60]) {
if (width !== 100) await r.resize(width);
const lines = (r.lastFrame() ?? "").split("\n");
const headerIndex = lines.findIndex((line) => line.includes("id suffix"));
const rowLines = suffixes.map((suffix) => lines.find((line) => line.includes(suffix)));

expect(headerIndex).toBeGreaterThanOrEqual(0);
expect(stringWidth(lines[headerIndex + 1]!)).toBe(width);
expect(rowLines.every((line) => line !== undefined)).toBe(true);
expect(new Set(rowLines.map((line) => lines.indexOf(line!))).size).toBe(suffixes.length);
expect(rowLines.every((line) => stringWidth(line!) <= width)).toBe(true);
expect(rowLines.every((line) => /[A-Za-z0-9]{10}\s+\d/.test(line!))).toBe(true);
}
});

test("filters against rendered timestamps and raw identifiers", async () => {
const core = new TestCoreClient();
core.runtime.setListResponse({ agentRuntimes: [runtime()] });
const r = renderScreen("/agentcore/runtime/list", { core });

await waitForText(r.lastFrame, "AbCdEf1234");
await r.write("/");
await r.write("2026-07-19 01:02");
await waitForText(r.lastFrame, "/ Filter: 2026-07-19 01:02");

expect(r.lastFrame()).toContain("AbCdEf1234");
expect(r.lastFrame()).not.toContain("No Runtimes found in this Region.");

await r.press("escape");
await r.write("/");
await r.write("orders-AbCdEf1234");
await waitForText(r.lastFrame, "/ Filter: orders-AbCdEf1234");

expect(r.lastFrame()).toContain("AbCdEf1234");
});
});
23 changes: 16 additions & 7 deletions src/components/RuntimeEndpointPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { AgentRuntimeEndpoint } from "@aws-sdk/client-bedrock-agentcore-con
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

interface RuntimeEndpointRow extends Record<string, unknown> {
qualifier: string;
Expand All @@ -12,6 +14,19 @@ interface RuntimeEndpointRow extends Record<string, unknown> {
lastUpdatedAt: string;
}

export const runtimeEndpointColumns = [
{ key: "qualifier", header: "qualifier", flex: true },
{ key: "liveVersion", header: "live", width: 6, minWidth: 5 },
{ key: "targetVersion", header: "target", width: 6 },
{ key: "status", header: "status", width: 13 },
{
key: "lastUpdatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<RuntimeEndpointRow>[];

function toRow(endpoint: AgentRuntimeEndpoint): RuntimeEndpointRow {
return {
qualifier: endpoint.name ?? endpoint.id ?? "",
Expand Down Expand Up @@ -54,13 +69,7 @@ export function RuntimeEndpointPicker({
};
}}
toRow={toRow}
columns={[
{ key: "qualifier", header: "qualifier" },
{ key: "liveVersion", header: "live" },
{ key: "targetVersion", header: "target" },
{ key: "status", header: "status" },
{ key: "lastUpdatedAt", header: "lastUpdatedAt" },
]}
columns={runtimeEndpointColumns}
getValue={(row) => row.qualifier}
onSelect={onSelect}
onBack={goBack}
Expand Down
33 changes: 26 additions & 7 deletions src/components/RuntimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { AgentRuntime } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

interface RuntimeRow extends Record<string, unknown> {
runtimeId: string;
Expand All @@ -12,6 +14,29 @@ interface RuntimeRow extends Record<string, unknown> {
lastUpdatedAt: string;
}

function runtimeIdSuffix(value: unknown): string {
const id = String(value ?? "");
return id.slice(id.lastIndexOf("-") + 1);
}

export const runtimeColumns = [
{ key: "runtimeName", header: "name", flex: true },
{
key: "runtimeId",
header: "id suffix",
width: 10,
render: runtimeIdSuffix,
},
{ key: "runtimeVersion", header: "version", width: 7 },
{ key: "status", header: "status", width: 13 },
{
key: "lastUpdatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<RuntimeRow>[];

function toRow(runtime: AgentRuntime): RuntimeRow {
const runtimeId = runtime.agentRuntimeId ?? "";
return {
Expand Down Expand Up @@ -53,13 +78,7 @@ export function RuntimePicker({
};
}}
toRow={toRow}
columns={[
{ key: "runtimeName", header: "name" },
{ key: "runtimeId", header: "id" },
{ key: "runtimeVersion", header: "latestVersion" },
{ key: "status", header: "status" },
{ key: "lastUpdatedAt", header: "lastUpdatedAt" },
]}
columns={runtimeColumns}
getValue={(row) => row.runtimeId}
onSelect={onSelect}
onBack={goBack}
Expand Down
Loading
Loading