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
32 changes: 32 additions & 0 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ import { MemoryEventListScreen } from "../handlers/memory/event/list/screen.tsx"
import { MemoryRecordScreen } from "../handlers/memory/record/screen.tsx";
import { MemoryRecordGetScreen } from "../handlers/memory/record/get/screen.tsx";
import { MemoryRecordListScreen } from "../handlers/memory/record/list/screen.tsx";
import { MemoryActorScreen } from "../handlers/memory/actor/screen.tsx";
import { MemoryActorListScreen } from "../handlers/memory/actor/list/screen.tsx";
import { MemorySessionScreen } from "../handlers/memory/session/screen.tsx";
import { MemorySessionListScreen } from "../handlers/memory/session/list/screen.tsx";
import { IdentityScreen } from "../handlers/identity/screen.tsx";
import { ApiKeyCredentialProviderScreen } from "../handlers/identity/api-key-credential-provider/screen.tsx";
import { ApiKeyCredentialProviderListScreen } from "../handlers/identity/api-key-credential-provider/list/screen.tsx";
Expand Down Expand Up @@ -414,6 +418,34 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
path="agentcore/memory/record/list/:memoryId/:scopeKind/:scope"
element={<MemoryRecordListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/actor"
element={<MemoryActorScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/actor/list"
element={<MemoryActorListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/actor/list/:memoryId"
element={<MemoryActorListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/session"
element={<MemorySessionScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/session/list"
element={<MemorySessionListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/session/list/:memoryId"
element={<MemorySessionListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/session/list/:memoryId/:actorId"
element={<MemorySessionListScreen ctx={ctx} core={core} />}
/>
<Route path="agentcore/identity" element={<IdentityScreen ctx={ctx} core={core} />} />
<Route
path="agentcore/identity/api-key-credential-provider"
Expand Down
104 changes: 104 additions & 0 deletions src/handlers/memory/actor/actor.screen.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { afterEach, describe, expect, test } from "bun:test";
import type { ActorSummary, SessionSummary } from "@aws-sdk/client-bedrock-agentcore";
import type { MemorySummary } from "@aws-sdk/client-bedrock-agentcore-control";
import {
cleanupScreens,
renderScreen,
TestCoreClient,
waitFor,
waitForText,
} from "../../../testing";

afterEach(cleanupScreens);

function memorySummary(overrides: Partial<MemorySummary> = {}): MemorySummary {
return {
arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/memory-1",
id: "memory-1",
status: "ACTIVE",
createdAt: new Date("2026-07-19T01:02:03.000Z"),
updatedAt: new Date("2026-07-20T12:34:56.000Z"),
...overrides,
};
}

function actor(overrides: Partial<ActorSummary> = {}): ActorSummary {
return {
actorId: "actor-1",
...overrides,
};
}

function session(overrides: Partial<SessionSummary> = {}): SessionSummary {
return {
sessionId: "session-1",
actorId: "actor-1",
createdAt: new Date("2026-08-02T10:20:30.000Z"),
...overrides,
};
}

describe("Memory actor list flow", () => {
test("uses the Memory picker, lists actors, then opens the actor's sessions", async () => {
const memoryId = "memory/blue one";
const actorId = "actor/blue one";
const core = new TestCoreClient();
core.memory.setListResponse({ memories: [memorySummary({ id: memoryId })] });
core.memory.setListActorsResponse({ actorSummaries: [actor({ actorId })] });
core.memory.setListSessionsResponse({
sessionSummaries: [session({ actorId })],
});
const screen = renderScreen("/agentcore/memory/actor/list", { core });

await waitForText(screen.lastFrame, memoryId);
await screen.press("return");
await waitForText(screen.lastFrame, actorId);
await screen.press("return");
await waitForText(screen.lastFrame, "session-1");

expect(core.memory.calls.find((call) => call.method === "listActors")).toEqual({
method: "listActors",
args: [
{
memoryId,
maxResults: expect.any(Number),
nextToken: undefined,
},
{ region: "us-east-1" },
],
});
expect(core.memory.calls.find((call) => call.method === "listSessions")).toEqual({
method: "listSessions",
args: [
{
memoryId,
actorId,
maxResults: expect.any(Number),
nextToken: undefined,
},
{ region: "us-east-1" },
],
});

await screen.press("escape");
await waitForText(screen.lastFrame, "choose an actor to list sessions for");
await screen.press("escape");
await waitForText(screen.lastFrame, "choose a Memory to list actors for");
});

test("shows empty and retry states for actor lists", async () => {
const empty = renderScreen("/agentcore/memory/actor/list/memory-1");
await waitForText(empty.lastFrame, "No actors found for Memory memory-1.");
empty.unmount();

const core = new TestCoreClient();
core.memory.setError(new Error("actors unavailable"));
const failed = renderScreen("/agentcore/memory/actor/list/memory-1", { core });

await waitForText(failed.lastFrame, "actors unavailable");
core.memory.setError(undefined);
core.memory.setListActorsResponse({ actorSummaries: [actor()] });
await failed.write("r");
await waitFor(() => failed.lastFrame()?.includes("actor-1") ?? false);
});
});
11 changes: 11 additions & 0 deletions src/handlers/memory/actor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Router } from "../../../router";
import { renderTui } from "../../../tui";
import type { AppIO } from "../../../io";
import type { Core } from "../../types";
import { createListMemoryActorsHandler } from "./list";

export function createMemoryActorHandler(core: Core, io: AppIO): Router {
return new Router("actor", "inspect actors in AgentCore Memories")
.default(renderTui(core, io))
.handler(createListMemoryActorsHandler(core));
}
33 changes: 33 additions & 0 deletions src/handlers/memory/actor/list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import z from "zod";
import { InputValidationError } from "../../../../errors";
import { createHandler, flag } from "../../../../router";
import { JsonRendererKey } from "../../../../tui";
import type { Core } from "../../../types";
import { coreOptsFromCtx } from "../../../utils";

export const createListMemoryActorsHandler = (core: Core) =>
createHandler({
name: "list",
description: "list actors in an AgentCore Memory",
flags: [
flag("memory", "the ID of the Memory", z.string().optional()),
flag("max-results", "maximum number of actors to return", z.number().optional()),
flag("next-token", "pagination token returned by a previous request", z.string().optional()),
],
handle: async (ctx, flags) => {
if (!flags.memory) {
throw new InputValidationError("required option '--memory <memory>' not specified");
}

const response = await core.memory.listActors(
{
memoryId: flags.memory,
maxResults: flags["max-results"],
nextToken: flags["next-token"],
},
coreOptsFromCtx(ctx),
);

ctx.require(JsonRendererKey).renderJson(response);
},
});
35 changes: 35 additions & 0 deletions src/handlers/memory/actor/list/screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useNavigate, useParams } from "react-router";
import { MemoryPicker } from "../../../../components/MemoryPicker";
import type { ScreenProps } from "../../../types";
import { MemoryActorPicker } from "../../listPickers";

export function MemoryActorListScreen(props: ScreenProps) {
const navigate = useNavigate();
const { memoryId } = useParams();

if (!memoryId) {
return (
<MemoryPicker
{...props}
breadcrumb={["agentcore", "memory", "actor", "list"]}
description="choose a Memory to list actors for"
onSelect={(id) => navigate(`/agentcore/memory/actor/list/${encodeURIComponent(id)}`)}
/>
);
}

return (
<MemoryActorPicker
{...props}
memoryId={memoryId}
breadcrumb={["agentcore", "memory", "actor", "list", memoryId]}
description="choose an actor to list sessions for"
onSelect={(actorId) =>
navigate(
`/agentcore/memory/session/list/${encodeURIComponent(memoryId)}/${encodeURIComponent(actorId)}`,
)
}
onBack={() => navigate(-1)}
/>
);
}
6 changes: 6 additions & 0 deletions src/handlers/memory/actor/screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RouterScreen } from "../../../components/RouterScreen";
import type { ScreenProps } from "../../types";

export function MemoryActorScreen(props: ScreenProps) {
return <RouterScreen {...props} path={["agentcore", "memory", "actor"]} />;
}
Loading
Loading