diff --git a/src/cli/tui/hooks/__tests__/useDevDeploy.test.tsx b/src/cli/tui/hooks/__tests__/useDevDeploy.test.tsx index 690055c55..d4130ccd0 100644 --- a/src/cli/tui/hooks/__tests__/useDevDeploy.test.tsx +++ b/src/cli/tui/hooks/__tests__/useDevDeploy.test.tsx @@ -36,10 +36,11 @@ vi.mock('../../../operations/deploy/change-detection', () => ({ })); function Harness({ skip }: { skip?: boolean }) { - const { steps, isComplete, error } = useDevDeploy({ skip }); + const { steps, isComplete, error, managedMemoryNotice } = useDevDeploy({ skip }); return ( - steps:{steps.length} isComplete:{String(isComplete)} error:{error ?? 'null'} + steps:{steps.length} isComplete:{String(isComplete)} error:{error ?? 'null'} notice: + {managedMemoryNotice ?? 'null'} ); } @@ -108,6 +109,31 @@ describe('useDevDeploy', () => { }); }); + it('surfaces the managed-memory heads-up from the onNotice callback', async () => { + mockHandleDeploy.mockImplementation((opts: { onNotice?: (message: string) => void }) => { + opts.onNotice?.('Managed memory: this harness automatically provisions a dedicated AgentCore Memory resource'); + return Promise.resolve({ success: true }); + }); + + const { lastFrame } = render(); + + await vi.waitFor(() => { + expect(lastFrame()).toContain('notice:Managed memory:'); + expect(lastFrame()).toContain('isComplete:true'); + }); + }); + + it('leaves the managed-memory heads-up null when onNotice is not called', async () => { + mockHandleDeploy.mockResolvedValue({ success: true }); + + const { lastFrame } = render(); + + await vi.waitFor(() => { + expect(lastFrame()).toContain('isComplete:true'); + }); + expect(lastFrame()).toContain('notice:null'); + }); + it('populates steps from onProgress callback', async () => { mockHandleDeploy.mockImplementation((opts: { onProgress?: (step: string, status: string) => void }) => { opts.onProgress?.('Validate project', 'start'); diff --git a/src/cli/tui/hooks/useDevDeploy.ts b/src/cli/tui/hooks/useDevDeploy.ts index a570676f4..d6615f8ee 100644 --- a/src/cli/tui/hooks/useDevDeploy.ts +++ b/src/cli/tui/hooks/useDevDeploy.ts @@ -18,6 +18,8 @@ export interface UseDevDeployResult { isComplete: boolean; error: string | undefined; logPath: string | undefined; + /** Managed-memory heads-up surfaced by handleDeploy (null when not applicable) */ + managedMemoryNotice: string | null; } export function useDevDeploy({ skip, ready = true }: UseDevDeployOptions = {}): UseDevDeployResult { @@ -26,6 +28,7 @@ export function useDevDeploy({ skip, ready = true }: UseDevDeployOptions = {}): const [deployDone, setDeployDone] = useState(false); const [error, setError] = useState(); const [logPath, setLogPath] = useState(); + const [managedMemoryNotice, setManagedMemoryNotice] = useState(null); const hasStarted = useRef(false); const onProgress = useCallback((stepName: string, status: 'start' | 'success' | 'error') => { @@ -41,6 +44,10 @@ export function useDevDeploy({ skip, ready = true }: UseDevDeployOptions = {}): setDeployMessages(prev => [...prev, msg]); }, []); + const onNotice = useCallback((message: string) => { + setManagedMemoryNotice(message); + }, []); + useEffect(() => { if (skip || !ready || hasStarted.current) return; hasStarted.current = true; @@ -78,6 +85,7 @@ export function useDevDeploy({ skip, ready = true }: UseDevDeployOptions = {}): verbose: true, onProgress, onDeployMessage, + onNotice, }); if (result.logPath) { @@ -95,10 +103,10 @@ export function useDevDeploy({ skip, ready = true }: UseDevDeployOptions = {}): }; void run(); - }, [skip, ready, onProgress, onDeployMessage]); + }, [skip, ready, onProgress, onDeployMessage, onNotice]); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- skip is boolean, not nullable; || is the correct operator here const isComplete = skip || deployDone; - return { steps, deployMessages, isComplete, error, logPath }; + return { steps, deployMessages, isComplete, error, logPath, managedMemoryNotice }; } diff --git a/src/cli/tui/screens/dev/DevScreen.tsx b/src/cli/tui/screens/dev/DevScreen.tsx index aa3abd89d..e52280b53 100644 --- a/src/cli/tui/screens/dev/DevScreen.tsx +++ b/src/cli/tui/screens/dev/DevScreen.tsx @@ -245,6 +245,7 @@ export function DevScreen(props: DevScreenProps) { isComplete: deployComplete, error: deployError, logPath: deployLogPath, + managedMemoryNotice, } = useDevDeploy({ skip: props.skipDeploy, ready: mode === 'deploying' }); const hasTransitionedFromDeployRef = useRef(false); @@ -527,6 +528,11 @@ export function DevScreen(props: DevScreenProps) { + {managedMemoryNotice && !deployComplete && ( + + Note: {managedMemoryNotice} + + )} {hasStartedCfn && ( diff --git a/src/cli/tui/screens/harness/AddHarnessScreen.tsx b/src/cli/tui/screens/harness/AddHarnessScreen.tsx index 3d94b85cb..60941f5f8 100644 --- a/src/cli/tui/screens/harness/AddHarnessScreen.tsx +++ b/src/cli/tui/screens/harness/AddHarnessScreen.tsx @@ -1423,7 +1423,10 @@ export function AddHarnessScreen({ initialValue="/mnt/data/" onSubmit={wizard.setSessionStoragePath} onCancel={() => wizard.goBack()} - customValidation={value => (value.startsWith('/') ? true : 'Must be an absolute path')} + customValidation={value => { + const r = validateBYOMountPath(value); + return r === true ? true : r; + }} /> )}