From e05690e00a94eb85dc4bc464bfd094c5d106dc24 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 11 Aug 2026 18:27:29 +0000 Subject: [PATCH 1/4] feat(memory): add contextual events action --- .../memory/event/event.screen.test.tsx | 45 ++++++++++++++++++- src/handlers/memory/get/screen.tsx | 13 +++--- src/handlers/memory/memory.screen.test.tsx | 23 ++++++---- 3 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/handlers/memory/event/event.screen.test.tsx b/src/handlers/memory/event/event.screen.test.tsx index 76f42fdea..e3edfc92b 100644 --- a/src/handlers/memory/event/event.screen.test.tsx +++ b/src/handlers/memory/event/event.screen.test.tsx @@ -1,6 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test"; import type { ActorSummary, Event, SessionSummary } from "@aws-sdk/client-bedrock-agentcore"; -import type { MemorySummary } from "@aws-sdk/client-bedrock-agentcore-control"; +import type { Memory, MemorySummary } from "@aws-sdk/client-bedrock-agentcore-control"; import { cleanupScreens, renderScreen, @@ -24,6 +24,22 @@ function memorySummary(overrides: Partial = {}): MemorySummary { }; } +function memory(overrides: Partial = {}): Memory { + return { + arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/memory-1", + id: "memory-1", + name: "orders-memory", + description: "Memory for the orders agent", + memoryExecutionRoleArn: "arn:aws:iam::123456789012:role/memory-role", + eventExpiryDuration: 30, + status: "ACTIVE", + createdAt: new Date("2026-07-19T01:02:03.000Z"), + updatedAt: new Date("2026-07-20T12:34:56.000Z"), + strategies: [], + ...overrides, + }; +} + function actor(overrides: Partial = {}): ActorSummary { return { actorId: "actor-1", @@ -109,6 +125,33 @@ describe("Memory event list flow", () => { await waitForText(screen.lastFrame, "choose a Memory to list"); }); + test("starts from Memory detail and preserves that origin through the scoped flow", async () => { + const core = new TestCoreClient(); + core.memory.setGetResponse({ memory: memory() }); + core.memory.setListActorsResponse({ actorSummaries: [actor()] }); + core.memory.setListSessionsResponse({ sessionSummaries: [session()] }); + core.memory.setListEventsResponse({ events: [event()] }); + const screen = renderScreen("/agentcore/memory/get/memory-1", { core }); + + await waitForText(screen.lastFrame, "browse this Memory's events"); + await screen.press("return"); + await waitForText(screen.lastFrame, "actor-1"); + expect(core.memory.calls.some((call) => call.method === "listMemories")).toBe(false); + + await screen.press("return"); + await waitForText(screen.lastFrame, "session-1"); + await screen.press("return"); + await waitForText(screen.lastFrame, "event-1"); + + await screen.press("escape"); + await waitForText(screen.lastFrame, "session-1"); + await screen.press("escape"); + await waitForText(screen.lastFrame, "actor-1"); + await screen.press("escape"); + await waitForText(screen.lastFrame, "agentcore → memory → get → memory-1"); + await waitForText(screen.lastFrame, "browse this Memory's events"); + }); + test("calls listEvents with the exact route scope and Core options", async () => { const core = new TestCoreClient(); core.memory.setListEventsResponse({ events: [event()] }); diff --git a/src/handlers/memory/get/screen.tsx b/src/handlers/memory/get/screen.tsx index 765dc9946..f3704bd9c 100644 --- a/src/handlers/memory/get/screen.tsx +++ b/src/handlers/memory/get/screen.tsx @@ -6,14 +6,9 @@ import type { ScreenProps } from "../../types"; import { coreOptsFromCtx } from "../../utils"; const ACTIONS = [ - { - name: "detail", - description: "show the full JSON definition", - to: (id: string) => `/agentcore/memory/get/${encodeURIComponent(id)}/json`, - }, { name: "events", - description: "list this Memory's events", + description: "browse this Memory's events", to: (id: string) => `/agentcore/memory/event/list/${encodeURIComponent(id)}`, }, { @@ -21,6 +16,11 @@ const ACTIONS = [ description: "list this Memory's records", to: (id: string) => `/agentcore/memory/record/list/${encodeURIComponent(id)}`, }, + { + name: "detail", + description: "show the full JSON definition", + to: (id: string) => `/agentcore/memory/get/${encodeURIComponent(id)}/json`, + }, ] as const; function useMemoryDetail({ ctx, core }: ScreenProps, memoryId: string | undefined) { @@ -64,7 +64,6 @@ export function MemoryGetScreen(props: ScreenProps) { } loadingLabel="Loading Memory…" onRetry={() => void detail.refetch()} - selectLabel="open detail" /> ); } diff --git a/src/handlers/memory/memory.screen.test.tsx b/src/handlers/memory/memory.screen.test.tsx index 9b064fbdf..2c98889eb 100644 --- a/src/handlers/memory/memory.screen.test.tsx +++ b/src/handlers/memory/memory.screen.test.tsx @@ -184,12 +184,17 @@ describe("Memory detail", () => { endpointUrl: memoryEndpointUrl, }); - await waitForText(screen.lastFrame, "show the full JSON definition"); + await waitForText(screen.lastFrame, "browse this Memory's events"); const frame = screen.lastFrame()!; expect(frame).toContain("orders-memory"); expect(frame).toMatch(/eventExpiryDays\s+30/); expect(frame).toMatch(/strategies\s+1/); expect(frame).toContain("arn:aws:bedrock-agentcore"); + expect(frame).toMatch(/❯ events\s+browse this Memory's events/); + expect(frame).toContain("list this Memory's records"); + expect(frame).toContain("show the full JSON definition"); + expect(frame).toContain("[enter] select"); + expect(frame).not.toContain("[enter] open detail"); expect(core.memory.calls.find((call) => call.method === "getMemory")).toEqual({ method: "getMemory", args: [ @@ -227,7 +232,9 @@ describe("Memory detail", () => { core.memory.setGetResponse(getMemoryOutput()); const screen = renderScreen("/agentcore/memory/get/memory-1", { core }); - await waitForText(screen.lastFrame, "show the full JSON definition"); + await waitForText(screen.lastFrame, "browse this Memory's events"); + await screen.press("down"); + await screen.press("down"); await screen.press("return"); await waitForText(screen.lastFrame, "agentcore → memory → get → memory-1 → json"); const frame = screen.lastFrame()!; @@ -244,8 +251,7 @@ describe("Memory detail", () => { await waitForText(screen.lastFrame, "memory-1"); await screen.press("return"); - await waitForText(screen.lastFrame, "list this Memory's events"); - await screen.press("down"); + await waitForText(screen.lastFrame, "browse this Memory's events"); await screen.press("return"); await waitForText(screen.lastFrame, "choose an actor to list sessions for"); @@ -255,10 +261,10 @@ describe("Memory detail", () => { }); await screen.press("escape"); - await waitForText(screen.lastFrame, "list this Memory's events"); + await waitForText(screen.lastFrame, "browse this Memory's events"); await screen.press("escape"); await waitForText(screen.lastFrame, "updated UTC"); - expect(screen.lastFrame()).not.toContain("list this Memory's events"); + expect(screen.lastFrame()).not.toContain("browse this Memory's events"); }); test("unwinds the record flow through Memory detail to the list", async () => { @@ -271,7 +277,6 @@ describe("Memory detail", () => { await screen.press("return"); await waitForText(screen.lastFrame, "list this Memory's records"); await screen.press("down"); - await screen.press("down"); await screen.press("return"); await waitForText(screen.lastFrame, "choose the namespace scope for the record list"); @@ -293,7 +298,7 @@ describe("Memory detail", () => { core.memory.setError(undefined); core.memory.setGetResponse(getMemoryOutput()); await screen.write("r"); - await waitForText(screen.lastFrame, "show the full JSON definition"); + await waitForText(screen.lastFrame, "browse this Memory's events"); }); test("does not open cached detail after a background refresh fails", async () => { @@ -306,7 +311,7 @@ describe("Memory detail", () => { }); const screen = renderScreen("/agentcore/memory/get/memory-1", { core, queryClient }); - await waitForText(screen.lastFrame, "show the full JSON definition"); + await waitForText(screen.lastFrame, "browse this Memory's events"); core.memory.setError(new Error("background refresh failed")); await queryClient.invalidateQueries({ queryKey: ["memory", "us-east-1", "memory-1", "full"], From 958d71d619c2ef905df87aa55e89c5720f1807aa Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Thu, 13 Aug 2026 15:49:35 +0000 Subject: [PATCH 2/4] fix(memory): restore event cursor after detail --- src/components/PaginatedTablePicker.tsx | 9 +++++++ src/components/ui/data-table/DataTable.tsx | 9 +++++-- .../memory/event/event.screen.test.tsx | 24 +++++++++++++++++++ src/handlers/memory/event/list/screen.tsx | 19 +++++++++++---- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/components/PaginatedTablePicker.tsx b/src/components/PaginatedTablePicker.tsx index 8b373f158..14f3ac552 100644 --- a/src/components/PaginatedTablePicker.tsx +++ b/src/components/PaginatedTablePicker.tsx @@ -20,6 +20,7 @@ export interface PaginatedTablePickerProps[]; sortRows?: (rows: TRow[]) => TRow[]; getValue: (row: TRow) => string | undefined; + initialValue?: string; onSelect: (value: string) => void; onBack: () => void; loadingMessage: string; @@ -38,6 +39,7 @@ export function PaginatedTablePicker columns, sortRows, getValue, + initialValue, onSelect, onBack, loadingMessage, @@ -57,6 +59,12 @@ export function PaginatedTablePicker const pageTransition = list.isFetching && !list.isPending; const mappedRows = (list.data?.items ?? []).map(toRow); const rows = sortRows ? sortRows(mappedRows) : mappedRows; + const initialSelectedRow = initialValue + ? Math.max( + 0, + rows.findIndex((row) => getValue(row) === initialValue), + ) + : 0; useInput( (input, key) => { @@ -106,6 +114,7 @@ export function PaginatedTablePicker showFooter={false} showDivider={true} pageSize={paging.pageSize} + initialSelectedRow={initialSelectedRow} selectionResetKey={paging.pageSize} focus={!pageTransition} columns={columns} diff --git a/src/components/ui/data-table/DataTable.tsx b/src/components/ui/data-table/DataTable.tsx index 2d9745fbe..b0ccb02d7 100644 --- a/src/components/ui/data-table/DataTable.tsx +++ b/src/components/ui/data-table/DataTable.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import cliTruncate from "cli-truncate"; import { Box, Text, useInput, useWindowSize } from "ink"; import stringWidth from "string-width"; @@ -47,6 +47,7 @@ export interface DataTableProps { showFooter?: boolean; emptyMessage?: string; focus?: boolean; + initialSelectedRow?: number; selectionResetKey?: string | number; theme?: InkUITheme; } @@ -70,16 +71,20 @@ export function DataTable>({ showFooter = true, emptyMessage = "No data", focus = true, + initialSelectedRow = 0, selectionResetKey, theme = darkTheme, }: DataTableProps): React.ReactElement { const { columns: terminalWidth } = useWindowSize(); - const [selectedRow, setSelectedRow] = useState(0); + const [selectedRow, setSelectedRow] = useState(() => Math.max(0, initialSelectedRow)); const [currentPage, setCurrentPage] = useState(0); const [searchQuery, setSearchQuery] = useState(""); const [searchMode, setSearchMode] = useState(false); + const selectionResetKeyRef = useRef(selectionResetKey); useEffect(() => { + if (selectionResetKeyRef.current === selectionResetKey) return; + selectionResetKeyRef.current = selectionResetKey; setSelectedRow(0); setCurrentPage(0); }, [selectionResetKey]); diff --git a/src/handlers/memory/event/event.screen.test.tsx b/src/handlers/memory/event/event.screen.test.tsx index e3edfc92b..2c95751ba 100644 --- a/src/handlers/memory/event/event.screen.test.tsx +++ b/src/handlers/memory/event/event.screen.test.tsx @@ -220,6 +220,30 @@ describe("Memory event list flow", () => { }); }); + test("restores the selected Event after returning from its JSON", async () => { + const selectedEvent = event({ eventId: "event-3" }); + const core = new TestCoreClient(); + core.memory.setListEventsResponse({ + events: [event({ eventId: "event-1" }), event({ eventId: "event-2" }), selectedEvent], + }); + core.memory.setGetEventResponse({ event: selectedEvent }); + const screen = renderScreen("/agentcore/memory/event/list/memory-1/actor-1/session-1", { + core, + }); + + await waitForText(screen.lastFrame, "event-3"); + await screen.press("down"); + await screen.press("down"); + await waitForText(screen.lastFrame, "❯ event-3"); + await screen.press("return"); + await waitForText(screen.lastFrame, '"eventId": "event-3"'); + + await screen.press("escape"); + await waitForText(screen.lastFrame, "occurred UTC"); + expect(screen.lastFrame()).toContain("❯ event-3"); + expect(screen.lastFrame()).not.toContain("❯ event-1"); + }); + test("paginates events and distinguishes a later-page empty state", async () => { const core = new TestCoreClient(); core.memory.setListEventsResponse({ diff --git a/src/handlers/memory/event/list/screen.tsx b/src/handlers/memory/event/list/screen.tsx index 71fd1ab64..12f3286ac 100644 --- a/src/handlers/memory/event/list/screen.tsx +++ b/src/handlers/memory/event/list/screen.tsx @@ -1,5 +1,5 @@ import type { ActorSummary, Event, SessionSummary } from "@aws-sdk/client-bedrock-agentcore"; -import { useNavigate, useParams } from "react-router"; +import { useLocation, useNavigate, useParams } from "react-router"; import { MemoryPicker } from "../../../../components/MemoryPicker"; import { PaginatedTablePicker } from "../../../../components/PaginatedTablePicker"; import { formatTimestamp } from "../../../../components/formatTimestamp"; @@ -155,9 +155,15 @@ interface EventPickerProps extends ScreenProps { sessionId: string; } +interface EventPickerLocationState { + selectedEventId?: string; +} + function EventPicker({ ctx, core, memoryId, actorId, sessionId }: EventPickerProps) { const opts = coreOptsFromCtx(ctx); + const location = useLocation(); const navigate = useNavigate(); + const locationState = (location.state as EventPickerLocationState | null) ?? {}; return ( row.eventId} - onSelect={(eventId) => + initialValue={locationState.selectedEventId} + onSelect={(eventId) => { + navigate(location.pathname, { + replace: true, + state: { ...locationState, selectedEventId: eventId }, + }); navigate( `/agentcore/memory/event/get/${encodeURIComponent(memoryId)}/${encodeURIComponent(actorId)}/${encodeURIComponent(sessionId)}/${encodeURIComponent(eventId)}`, - ) - } + ); + }} onBack={() => navigate(-1)} loadingMessage={`Loading events for session ${sessionId}...`} errorMessage={(error) => `Error loading events for session ${sessionId}: ${error.message}`} From 959522fc4a0e490091bc5dbfbaa58f7ecf714909 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Thu, 13 Aug 2026 16:36:24 +0000 Subject: [PATCH 3/4] fix(tui): restore shared picker selection --- src/components/PaginatedTablePicker.test.tsx | 23 +++++++++++++++ src/components/PaginatedTablePicker.tsx | 31 ++++++++++++++++---- src/handlers/memory/event/list/screen.tsx | 19 +++--------- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/components/PaginatedTablePicker.test.tsx b/src/components/PaginatedTablePicker.test.tsx index 6069d3119..708e7a461 100644 --- a/src/components/PaginatedTablePicker.test.tsx +++ b/src/components/PaginatedTablePicker.test.tsx @@ -180,6 +180,29 @@ describe("paginated table picker contract", () => { ).toBe(false); }); + test("restores the selected row after returning from its detail screen", async () => { + const selected = harness({ harnessName: "third-harness", harnessId: "third-harness" }); + const core = coreWith([ + harness({ harnessName: "first-harness", harnessId: "first-harness" }), + harness({ harnessName: "second-harness", harnessId: "second-harness" }), + selected, + ]); + core.harness.setGetResponse(getResponse(selected)); + const r = renderScreen("/agentcore/harness/list", { core }); + + await waitForText(r.lastFrame, "third-harness"); + await r.press("down"); + await r.press("down"); + await waitForText(r.lastFrame, "❯ third-harness"); + await r.press("return"); + await waitForText(r.lastFrame, "agentcore → harness → get → third-harness"); + + await r.press("escape"); + await waitForText(r.lastFrame, "updated UTC"); + expect(r.lastFrame()).toContain("❯ third-harness"); + expect(r.lastFrame()).not.toContain("❯ first-harness"); + }); + test("retains rows and disables selection and paging during a transition", async () => { const core = new TestCoreClient(); core.harness.setListResponse({ diff --git a/src/components/PaginatedTablePicker.tsx b/src/components/PaginatedTablePicker.tsx index 14f3ac552..b915dd49d 100644 --- a/src/components/PaginatedTablePicker.tsx +++ b/src/components/PaginatedTablePicker.tsx @@ -1,5 +1,6 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { Text, useInput } from "ink"; +import { useLocation, useNavigate } from "react-router"; import { Layout } from "./Layout"; import { usePagedList } from "./usePagedList"; import { darkTheme } from "./ui/_core.js"; @@ -11,6 +12,10 @@ export interface TokenPage { nextToken?: string; } +interface PaginatedTableLocationState extends Record { + paginatedTableSelection?: string; +} + export interface PaginatedTablePickerProps> { breadcrumb: string[]; description?: string; @@ -20,7 +25,6 @@ export interface PaginatedTablePickerProps[]; sortRows?: (rows: TRow[]) => TRow[]; getValue: (row: TRow) => string | undefined; - initialValue?: string; onSelect: (value: string) => void; onBack: () => void; loadingMessage: string; @@ -39,7 +43,6 @@ export function PaginatedTablePicker columns, sortRows, getValue, - initialValue, onSelect, onBack, loadingMessage, @@ -48,6 +51,8 @@ export function PaginatedTablePicker emptyPageMessage, maxPageSize, }: PaginatedTablePickerProps) { + const location = useLocation(); + const navigate = useNavigate(); const paging = usePagedList(maxPageSize); const list = useQuery({ queryKey: [...queryKey, paging.pageSize, paging.token], @@ -59,10 +64,14 @@ export function PaginatedTablePicker const pageTransition = list.isFetching && !list.isPending; const mappedRows = (list.data?.items ?? []).map(toRow); const rows = sortRows ? sortRows(mappedRows) : mappedRows; - const initialSelectedRow = initialValue + const locationState = + location.state && typeof location.state === "object" + ? (location.state as PaginatedTableLocationState) + : {}; + const initialSelectedRow = locationState.paginatedTableSelection ? Math.max( 0, - rows.findIndex((row) => getValue(row) === initialValue), + rows.findIndex((row) => getValue(row) === locationState.paginatedTableSelection), ) : 0; @@ -122,7 +131,19 @@ export function PaginatedTablePicker emptyMessage={paginated ? emptyPageMessage : emptyMessage} onSelect={(row) => { const value = getValue(row); - if (value) onSelect(value); + if (!value) return; + navigate( + { + pathname: location.pathname, + search: location.search, + hash: location.hash, + }, + { + replace: true, + state: { ...locationState, paginatedTableSelection: value }, + }, + ); + onSelect(value); }} onEscape={onBack} onPrevPage={!pageTransition && paging.pageIndex > 0 ? paging.prev : undefined} diff --git a/src/handlers/memory/event/list/screen.tsx b/src/handlers/memory/event/list/screen.tsx index 12f3286ac..71fd1ab64 100644 --- a/src/handlers/memory/event/list/screen.tsx +++ b/src/handlers/memory/event/list/screen.tsx @@ -1,5 +1,5 @@ import type { ActorSummary, Event, SessionSummary } from "@aws-sdk/client-bedrock-agentcore"; -import { useLocation, useNavigate, useParams } from "react-router"; +import { useNavigate, useParams } from "react-router"; import { MemoryPicker } from "../../../../components/MemoryPicker"; import { PaginatedTablePicker } from "../../../../components/PaginatedTablePicker"; import { formatTimestamp } from "../../../../components/formatTimestamp"; @@ -155,15 +155,9 @@ interface EventPickerProps extends ScreenProps { sessionId: string; } -interface EventPickerLocationState { - selectedEventId?: string; -} - function EventPicker({ ctx, core, memoryId, actorId, sessionId }: EventPickerProps) { const opts = coreOptsFromCtx(ctx); - const location = useLocation(); const navigate = useNavigate(); - const locationState = (location.state as EventPickerLocationState | null) ?? {}; return ( row.eventId} - initialValue={locationState.selectedEventId} - onSelect={(eventId) => { - navigate(location.pathname, { - replace: true, - state: { ...locationState, selectedEventId: eventId }, - }); + onSelect={(eventId) => navigate( `/agentcore/memory/event/get/${encodeURIComponent(memoryId)}/${encodeURIComponent(actorId)}/${encodeURIComponent(sessionId)}/${encodeURIComponent(eventId)}`, - ); - }} + ) + } onBack={() => navigate(-1)} loadingMessage={`Loading events for session ${sessionId}...`} errorMessage={(error) => `Error loading events for session ${sessionId}: ${error.message}`} From c57c5894f2b14d90fa455f9bce5006b20361ed10 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Thu, 13 Aug 2026 16:39:11 +0000 Subject: [PATCH 4/4] refactor(tui): narrow cursor restoration scope --- .../memory/event/event.screen.test.tsx | 45 +------------------ src/handlers/memory/get/screen.tsx | 13 +++--- src/handlers/memory/memory.screen.test.tsx | 23 ++++------ 3 files changed, 17 insertions(+), 64 deletions(-) diff --git a/src/handlers/memory/event/event.screen.test.tsx b/src/handlers/memory/event/event.screen.test.tsx index 2c95751ba..337b7b082 100644 --- a/src/handlers/memory/event/event.screen.test.tsx +++ b/src/handlers/memory/event/event.screen.test.tsx @@ -1,6 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test"; import type { ActorSummary, Event, SessionSummary } from "@aws-sdk/client-bedrock-agentcore"; -import type { Memory, MemorySummary } from "@aws-sdk/client-bedrock-agentcore-control"; +import type { MemorySummary } from "@aws-sdk/client-bedrock-agentcore-control"; import { cleanupScreens, renderScreen, @@ -24,22 +24,6 @@ function memorySummary(overrides: Partial = {}): MemorySummary { }; } -function memory(overrides: Partial = {}): Memory { - return { - arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/memory-1", - id: "memory-1", - name: "orders-memory", - description: "Memory for the orders agent", - memoryExecutionRoleArn: "arn:aws:iam::123456789012:role/memory-role", - eventExpiryDuration: 30, - status: "ACTIVE", - createdAt: new Date("2026-07-19T01:02:03.000Z"), - updatedAt: new Date("2026-07-20T12:34:56.000Z"), - strategies: [], - ...overrides, - }; -} - function actor(overrides: Partial = {}): ActorSummary { return { actorId: "actor-1", @@ -125,33 +109,6 @@ describe("Memory event list flow", () => { await waitForText(screen.lastFrame, "choose a Memory to list"); }); - test("starts from Memory detail and preserves that origin through the scoped flow", async () => { - const core = new TestCoreClient(); - core.memory.setGetResponse({ memory: memory() }); - core.memory.setListActorsResponse({ actorSummaries: [actor()] }); - core.memory.setListSessionsResponse({ sessionSummaries: [session()] }); - core.memory.setListEventsResponse({ events: [event()] }); - const screen = renderScreen("/agentcore/memory/get/memory-1", { core }); - - await waitForText(screen.lastFrame, "browse this Memory's events"); - await screen.press("return"); - await waitForText(screen.lastFrame, "actor-1"); - expect(core.memory.calls.some((call) => call.method === "listMemories")).toBe(false); - - await screen.press("return"); - await waitForText(screen.lastFrame, "session-1"); - await screen.press("return"); - await waitForText(screen.lastFrame, "event-1"); - - await screen.press("escape"); - await waitForText(screen.lastFrame, "session-1"); - await screen.press("escape"); - await waitForText(screen.lastFrame, "actor-1"); - await screen.press("escape"); - await waitForText(screen.lastFrame, "agentcore → memory → get → memory-1"); - await waitForText(screen.lastFrame, "browse this Memory's events"); - }); - test("calls listEvents with the exact route scope and Core options", async () => { const core = new TestCoreClient(); core.memory.setListEventsResponse({ events: [event()] }); diff --git a/src/handlers/memory/get/screen.tsx b/src/handlers/memory/get/screen.tsx index f3704bd9c..765dc9946 100644 --- a/src/handlers/memory/get/screen.tsx +++ b/src/handlers/memory/get/screen.tsx @@ -6,9 +6,14 @@ import type { ScreenProps } from "../../types"; import { coreOptsFromCtx } from "../../utils"; const ACTIONS = [ + { + name: "detail", + description: "show the full JSON definition", + to: (id: string) => `/agentcore/memory/get/${encodeURIComponent(id)}/json`, + }, { name: "events", - description: "browse this Memory's events", + description: "list this Memory's events", to: (id: string) => `/agentcore/memory/event/list/${encodeURIComponent(id)}`, }, { @@ -16,11 +21,6 @@ const ACTIONS = [ description: "list this Memory's records", to: (id: string) => `/agentcore/memory/record/list/${encodeURIComponent(id)}`, }, - { - name: "detail", - description: "show the full JSON definition", - to: (id: string) => `/agentcore/memory/get/${encodeURIComponent(id)}/json`, - }, ] as const; function useMemoryDetail({ ctx, core }: ScreenProps, memoryId: string | undefined) { @@ -64,6 +64,7 @@ export function MemoryGetScreen(props: ScreenProps) { } loadingLabel="Loading Memory…" onRetry={() => void detail.refetch()} + selectLabel="open detail" /> ); } diff --git a/src/handlers/memory/memory.screen.test.tsx b/src/handlers/memory/memory.screen.test.tsx index 2c98889eb..9b064fbdf 100644 --- a/src/handlers/memory/memory.screen.test.tsx +++ b/src/handlers/memory/memory.screen.test.tsx @@ -184,17 +184,12 @@ describe("Memory detail", () => { endpointUrl: memoryEndpointUrl, }); - await waitForText(screen.lastFrame, "browse this Memory's events"); + await waitForText(screen.lastFrame, "show the full JSON definition"); const frame = screen.lastFrame()!; expect(frame).toContain("orders-memory"); expect(frame).toMatch(/eventExpiryDays\s+30/); expect(frame).toMatch(/strategies\s+1/); expect(frame).toContain("arn:aws:bedrock-agentcore"); - expect(frame).toMatch(/❯ events\s+browse this Memory's events/); - expect(frame).toContain("list this Memory's records"); - expect(frame).toContain("show the full JSON definition"); - expect(frame).toContain("[enter] select"); - expect(frame).not.toContain("[enter] open detail"); expect(core.memory.calls.find((call) => call.method === "getMemory")).toEqual({ method: "getMemory", args: [ @@ -232,9 +227,7 @@ describe("Memory detail", () => { core.memory.setGetResponse(getMemoryOutput()); const screen = renderScreen("/agentcore/memory/get/memory-1", { core }); - await waitForText(screen.lastFrame, "browse this Memory's events"); - await screen.press("down"); - await screen.press("down"); + await waitForText(screen.lastFrame, "show the full JSON definition"); await screen.press("return"); await waitForText(screen.lastFrame, "agentcore → memory → get → memory-1 → json"); const frame = screen.lastFrame()!; @@ -251,7 +244,8 @@ describe("Memory detail", () => { await waitForText(screen.lastFrame, "memory-1"); await screen.press("return"); - await waitForText(screen.lastFrame, "browse this Memory's events"); + await waitForText(screen.lastFrame, "list this Memory's events"); + await screen.press("down"); await screen.press("return"); await waitForText(screen.lastFrame, "choose an actor to list sessions for"); @@ -261,10 +255,10 @@ describe("Memory detail", () => { }); await screen.press("escape"); - await waitForText(screen.lastFrame, "browse this Memory's events"); + await waitForText(screen.lastFrame, "list this Memory's events"); await screen.press("escape"); await waitForText(screen.lastFrame, "updated UTC"); - expect(screen.lastFrame()).not.toContain("browse this Memory's events"); + expect(screen.lastFrame()).not.toContain("list this Memory's events"); }); test("unwinds the record flow through Memory detail to the list", async () => { @@ -277,6 +271,7 @@ describe("Memory detail", () => { await screen.press("return"); await waitForText(screen.lastFrame, "list this Memory's records"); await screen.press("down"); + await screen.press("down"); await screen.press("return"); await waitForText(screen.lastFrame, "choose the namespace scope for the record list"); @@ -298,7 +293,7 @@ describe("Memory detail", () => { core.memory.setError(undefined); core.memory.setGetResponse(getMemoryOutput()); await screen.write("r"); - await waitForText(screen.lastFrame, "browse this Memory's events"); + await waitForText(screen.lastFrame, "show the full JSON definition"); }); test("does not open cached detail after a background refresh fails", async () => { @@ -311,7 +306,7 @@ describe("Memory detail", () => { }); const screen = renderScreen("/agentcore/memory/get/memory-1", { core, queryClient }); - await waitForText(screen.lastFrame, "browse this Memory's events"); + await waitForText(screen.lastFrame, "show the full JSON definition"); core.memory.setError(new Error("background refresh failed")); await queryClient.invalidateQueries({ queryKey: ["memory", "us-east-1", "memory-1", "full"],